@getbrevo/cli 1.1.0 → 2.0.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 +30 -0
- package/README.md +19 -2
- package/agent-context/AGENTS.md +16 -5
- package/agent-context/SKILL.md +17 -3
- package/dist/api/client.d.ts +2 -0
- package/dist/api/client.d.ts.map +1 -1
- package/dist/api/client.js +50 -36
- package/dist/api/client.js.map +1 -1
- package/dist/bin/index.js +19 -4
- package/dist/bin/index.js.map +1 -1
- package/dist/commands/app/create.d.ts.map +1 -1
- package/dist/commands/app/create.js +240 -221
- package/dist/commands/app/create.js.map +1 -1
- package/dist/commands/app/delete.d.ts.map +1 -1
- package/dist/commands/app/delete.js +81 -62
- package/dist/commands/app/delete.js.map +1 -1
- package/dist/commands/app/list.d.ts.map +1 -1
- package/dist/commands/app/list.js +10 -3
- package/dist/commands/app/list.js.map +1 -1
- package/dist/commands/app/scaffold.d.ts.map +1 -1
- package/dist/commands/app/scaffold.js +12 -2
- package/dist/commands/app/scaffold.js.map +1 -1
- package/dist/commands/app/scopes.d.ts.map +1 -1
- package/dist/commands/app/scopes.js +2 -0
- package/dist/commands/app/scopes.js.map +1 -1
- package/dist/commands/app/start.d.ts.map +1 -1
- package/dist/commands/app/start.js +7 -0
- package/dist/commands/app/start.js.map +1 -1
- package/dist/commands/app/update.d.ts.map +1 -1
- package/dist/commands/app/update.js +297 -266
- package/dist/commands/app/update.js.map +1 -1
- package/dist/commands/login.d.ts.map +1 -1
- package/dist/commands/login.js +154 -138
- package/dist/commands/login.js.map +1 -1
- package/dist/commands/whoami.d.ts.map +1 -1
- package/dist/commands/whoami.js +27 -20
- package/dist/commands/whoami.js.map +1 -1
- package/dist/lang/en.d.ts +23 -1
- package/dist/lang/en.d.ts.map +1 -1
- package/dist/lang/en.js +25 -1
- package/dist/lang/en.js.map +1 -1
- package/dist/lib/auth-guard.d.ts.map +1 -1
- package/dist/lib/auth-guard.js +10 -1
- package/dist/lib/auth-guard.js.map +1 -1
- package/dist/lib/constants.d.ts +10 -3
- package/dist/lib/constants.d.ts.map +1 -1
- package/dist/lib/constants.js +19 -4
- package/dist/lib/constants.js.map +1 -1
- package/dist/lib/logger.d.ts.map +1 -1
- package/dist/lib/logger.js +16 -3
- package/dist/lib/logger.js.map +1 -1
- package/dist/lib/telemetry.d.ts +9 -0
- package/dist/lib/telemetry.d.ts.map +1 -0
- package/dist/lib/telemetry.js +57 -0
- package/dist/lib/telemetry.js.map +1 -0
- package/dist/lib/update-notifier.d.ts +3 -0
- package/dist/lib/update-notifier.d.ts.map +1 -1
- package/dist/lib/update-notifier.js +77 -34
- package/dist/lib/update-notifier.js.map +1 -1
- package/dist/lib/validators.d.ts +6 -0
- package/dist/lib/validators.d.ts.map +1 -1
- package/dist/lib/validators.js +10 -0
- package/dist/lib/validators.js.map +1 -1
- package/dist/services/app.d.ts +1 -1
- package/dist/services/app.d.ts.map +1 -1
- package/dist/services/app.js +6 -6
- package/dist/services/app.js.map +1 -1
- package/dist/services/scopes-html.d.ts.map +1 -1
- package/dist/services/scopes-html.js +208 -29
- package/dist/services/scopes-html.js.map +1 -1
- package/dist/services/skill.js +1 -1
- package/dist/services/skill.js.map +1 -1
- package/dist/types.d.ts +2 -2
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -2
|
@@ -48,196 +48,230 @@ const container_1 = require("../../container");
|
|
|
48
48
|
const ui_1 = require("../../lib/ui");
|
|
49
49
|
const config_1 = require("../../lib/config");
|
|
50
50
|
const validators_1 = require("../../lib/validators");
|
|
51
|
+
const constants_1 = require("../../lib/constants");
|
|
51
52
|
const inquirer_1 = __importDefault(require("inquirer"));
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
if (nameCheck !== true)
|
|
62
|
-
throw new errors_1.CliError(nameCheck);
|
|
53
|
+
const NON_INTERACTIVE_CONFIRM_ERROR = 'Cannot prompt for confirmation in non-interactive mode. Use --yes or --json to skip.';
|
|
54
|
+
// File exists but readProjectConfig returned null — invalid JSON or missing appId
|
|
55
|
+
function assertConfigFileUsable() {
|
|
56
|
+
const configPath = path.resolve(process.cwd(), 'app-config.json');
|
|
57
|
+
if (!fs.existsSync(configPath))
|
|
58
|
+
return;
|
|
59
|
+
let raw;
|
|
60
|
+
try {
|
|
61
|
+
raw = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
63
62
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
// is non-null.
|
|
67
|
-
const configAppId = config?.appId;
|
|
68
|
-
const configAppIdValid = !!configAppId;
|
|
69
|
-
// app-config.json is only usable when it describes the app being updated.
|
|
70
|
-
// When --app-id differs from config.appId, the local file belongs to a
|
|
71
|
-
// different app and must not be used as the update payload or merge base.
|
|
72
|
-
const configMatches = configAppIdValid && (!options.appId || options.appId === configAppId);
|
|
73
|
-
// No flags and no usable config: emit a specific error.
|
|
74
|
-
if (!hasFlags && !configMatches) {
|
|
75
|
-
// File exists but appId is empty/invalid — surface that over a
|
|
76
|
-
// mismatch error, which would otherwise quote a garbage configAppId.
|
|
77
|
-
if (config != null && !configAppIdValid) {
|
|
78
|
-
throw new errors_1.CliError(en_1.messages.APP_UPDATE_INVALID_APP_ID);
|
|
79
|
-
}
|
|
80
|
-
if (config != null && options.appId && options.appId !== configAppId) {
|
|
81
|
-
throw new errors_1.CliError(en_1.messages.APP_UPDATE_APP_ID_MISMATCH(options.appId, configAppId));
|
|
82
|
-
}
|
|
83
|
-
const configExists = fs.existsSync(path.resolve(process.cwd(), 'app-config.json'));
|
|
84
|
-
if (configExists) {
|
|
85
|
-
// File exists but readProjectConfig returned null — invalid JSON or missing appId
|
|
86
|
-
let raw;
|
|
87
|
-
try {
|
|
88
|
-
raw = JSON.parse(fs.readFileSync(path.resolve(process.cwd(), 'app-config.json'), 'utf-8'));
|
|
89
|
-
}
|
|
90
|
-
catch {
|
|
91
|
-
throw new errors_1.CliError(en_1.messages.APP_UPDATE_INVALID_JSON);
|
|
92
|
-
}
|
|
93
|
-
if (!raw ||
|
|
94
|
-
typeof raw !== 'object' ||
|
|
95
|
-
!('appId' in raw) ||
|
|
96
|
-
!raw.appId) {
|
|
97
|
-
throw new errors_1.CliError(en_1.messages.APP_UPDATE_MISSING_APP_ID);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
throw new errors_1.CliError(en_1.messages.APP_UPDATE_NOTHING_TO_UPDATE);
|
|
63
|
+
catch {
|
|
64
|
+
throw new errors_1.CliError(en_1.messages.APP_UPDATE_INVALID_JSON);
|
|
101
65
|
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
appId
|
|
66
|
+
if (!raw ||
|
|
67
|
+
typeof raw !== 'object' ||
|
|
68
|
+
!('appId' in raw) ||
|
|
69
|
+
!raw.appId) {
|
|
70
|
+
throw new errors_1.CliError(en_1.messages.APP_UPDATE_MISSING_APP_ID);
|
|
106
71
|
}
|
|
107
|
-
|
|
108
|
-
|
|
72
|
+
}
|
|
73
|
+
// No flags and no usable config: emit a specific error.
|
|
74
|
+
function throwNoUpdateSource(config, configAppIdValid, configAppId, flagAppId) {
|
|
75
|
+
// File exists but appId is empty/invalid — surface that over a
|
|
76
|
+
// mismatch error, which would otherwise quote a garbage configAppId.
|
|
77
|
+
if (config != null && !configAppIdValid) {
|
|
78
|
+
throw new errors_1.CliError(en_1.messages.APP_UPDATE_INVALID_APP_ID);
|
|
79
|
+
}
|
|
80
|
+
if (config != null && flagAppId && flagAppId !== configAppId) {
|
|
81
|
+
throw new errors_1.CliError(en_1.messages.APP_UPDATE_APP_ID_MISMATCH(flagAppId, configAppId));
|
|
109
82
|
}
|
|
110
|
-
|
|
83
|
+
assertConfigFileUsable();
|
|
84
|
+
throw new errors_1.CliError(en_1.messages.APP_UPDATE_NOTHING_TO_UPDATE);
|
|
85
|
+
}
|
|
86
|
+
// Resolve appId: flag > config
|
|
87
|
+
function resolveAppId(flagAppId, configAppId, config) {
|
|
88
|
+
if (flagAppId)
|
|
89
|
+
return flagAppId;
|
|
90
|
+
if (configAppId)
|
|
91
|
+
return configAppId;
|
|
92
|
+
if (config)
|
|
111
93
|
throw new errors_1.CliError(en_1.messages.APP_UPDATE_INVALID_APP_ID);
|
|
94
|
+
throw new errors_1.CliError(en_1.messages.APP_UPDATE_NO_APP_RESOLVED);
|
|
95
|
+
}
|
|
96
|
+
async function confirmUpdate() {
|
|
97
|
+
const { confirmed } = await inquirer_1.default.prompt([
|
|
98
|
+
{
|
|
99
|
+
type: 'confirm',
|
|
100
|
+
name: 'confirmed',
|
|
101
|
+
message: en_1.messages.APP_UPDATE_CONFIRM,
|
|
102
|
+
default: true,
|
|
103
|
+
},
|
|
104
|
+
]);
|
|
105
|
+
if (!confirmed) {
|
|
106
|
+
(0, logger_1.logInfo)(`\n ${en_1.messages.APP_UPDATE_CANCELLED}\n`);
|
|
107
|
+
return false;
|
|
112
108
|
}
|
|
113
|
-
|
|
114
|
-
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
function reportUpdateResult(params, jsonMode) {
|
|
112
|
+
if (jsonMode) {
|
|
113
|
+
(0, json_output_1.jsonOutput)({
|
|
114
|
+
app_id: params.appId,
|
|
115
|
+
name: params.name,
|
|
116
|
+
redirect_uris: params.redirectUris,
|
|
117
|
+
scopes: params.scopes,
|
|
118
|
+
...(params.logoUri ? { logo_uri: params.logoUri } : {}),
|
|
119
|
+
});
|
|
120
|
+
return;
|
|
115
121
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
if (!options.json && !options.yes) {
|
|
149
|
-
const { confirmed } = await inquirer_1.default.prompt([
|
|
150
|
-
{
|
|
151
|
-
type: 'confirm',
|
|
152
|
-
name: 'confirmed',
|
|
153
|
-
message: en_1.messages.APP_UPDATE_CONFIRM,
|
|
154
|
-
default: true,
|
|
155
|
-
},
|
|
156
|
-
]);
|
|
157
|
-
if (!confirmed) {
|
|
158
|
-
(0, logger_1.logInfo)(`\n ${en_1.messages.APP_UPDATE_CANCELLED}\n`);
|
|
159
|
-
return;
|
|
160
|
-
}
|
|
122
|
+
(0, logger_1.logSuccess)(en_1.messages.APP_UPDATE_SUCCESS);
|
|
123
|
+
if (params.name) {
|
|
124
|
+
(0, logger_1.logInfo)(` Name: ${params.name}`);
|
|
125
|
+
}
|
|
126
|
+
(0, logger_1.logInfo)(` Redirect URLs: ${params.redirectUris.length > 0 ? params.redirectUris.join(', ') : '(none)'}`);
|
|
127
|
+
(0, logger_1.logInfo)(` Scopes: ${params.scopes.length > 0 ? params.scopes.join(', ') : '(none)'}`);
|
|
128
|
+
if (params.logoUri) {
|
|
129
|
+
(0, logger_1.logInfo)(` Logo URL: ${params.logoUri}`);
|
|
130
|
+
}
|
|
131
|
+
if (params.configWrittenBack) {
|
|
132
|
+
(0, logger_1.logInfo)(' app-config.json updated.');
|
|
133
|
+
}
|
|
134
|
+
process.stdout.write('\n');
|
|
135
|
+
}
|
|
136
|
+
// No flags provided: push full config from app-config.json.
|
|
137
|
+
async function pushFullConfig(appId, config, options) {
|
|
138
|
+
const redirectUrls = config.auth?.redirectUrls;
|
|
139
|
+
if (!redirectUrls || redirectUrls.length === 0) {
|
|
140
|
+
throw new errors_1.CliError(en_1.messages.APP_UPDATE_NO_REDIRECT_URLS);
|
|
141
|
+
}
|
|
142
|
+
validateRedirectUrls(redirectUrls);
|
|
143
|
+
const nextScopes = config.auth?.scopes ?? [];
|
|
144
|
+
(0, validators_1.validateScopes)(nextScopes);
|
|
145
|
+
// The legacy 'all' scope is deprecated — block the push and point at the
|
|
146
|
+
// migration path (--scope). No silent rewrite, no escape hatch (BEX-214).
|
|
147
|
+
if ((0, validators_1.containsLegacyAllScope)(nextScopes)) {
|
|
148
|
+
throw new errors_1.CliError(en_1.messages.LEGACY_ALL_SCOPE_DEPRECATED_BLOCK);
|
|
149
|
+
}
|
|
150
|
+
if (!options.json) {
|
|
151
|
+
// Fail fast before the network fetch when we'd have nowhere to show the diff.
|
|
152
|
+
if (!options.yes && !process.stdin.isTTY) {
|
|
153
|
+
throw new errors_1.CliError(NON_INTERACTIVE_CONFIRM_ERROR);
|
|
161
154
|
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
155
|
+
// Fetch current remote state so the summary can show deltas vs. what the
|
|
156
|
+
// push will apply. Hard-fail on fetch error — the user asked for a diff.
|
|
157
|
+
const remote = await fetchExistingApp(appId, options.json);
|
|
158
|
+
renderUpdateSummary({
|
|
159
|
+
appId,
|
|
160
|
+
currentName: remote.name,
|
|
161
|
+
nextName: config.appName,
|
|
162
|
+
currentUrls: remote.redirect_uris ?? [],
|
|
163
|
+
nextUrls: redirectUrls,
|
|
164
|
+
currentLogoUri: remote.logo_uri,
|
|
165
|
+
nextLogoUri: config.logoUri,
|
|
166
|
+
currentScopes: remote.scopes ?? [],
|
|
167
|
+
nextScopes,
|
|
168
168
|
});
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
(0, config_1.saveAppName)(appId, config.appName);
|
|
172
|
-
if (options.json) {
|
|
173
|
-
(0, json_output_1.jsonOutput)({
|
|
174
|
-
app_id: appId,
|
|
175
|
-
name: config.appName,
|
|
176
|
-
redirect_uris: redirectUrls,
|
|
177
|
-
scopes: nextScopes,
|
|
178
|
-
...(config.logoUri ? { logo_uri: config.logoUri } : {}),
|
|
179
|
-
});
|
|
180
|
-
return;
|
|
181
|
-
}
|
|
182
|
-
(0, logger_1.logSuccess)(en_1.messages.APP_UPDATE_SUCCESS);
|
|
183
|
-
if (config.appName) {
|
|
184
|
-
(0, logger_1.logInfo)(` Name: ${config.appName}`);
|
|
185
|
-
}
|
|
186
|
-
(0, logger_1.logInfo)(` Redirect URLs: ${redirectUrls.join(', ')}`);
|
|
187
|
-
(0, logger_1.logInfo)(` Scopes: ${nextScopes.length > 0 ? nextScopes.join(', ') : '(none)'}`);
|
|
188
|
-
if (config.logoUri) {
|
|
189
|
-
(0, logger_1.logInfo)(` Logo URL: ${config.logoUri}`);
|
|
190
|
-
}
|
|
191
|
-
process.stdout.write('\n');
|
|
169
|
+
}
|
|
170
|
+
if (!options.json && !options.yes && !(await confirmUpdate())) {
|
|
192
171
|
return;
|
|
193
172
|
}
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
173
|
+
const spinner = (0, ui_1.createSpinner)('Updating app...', { silent: options.json });
|
|
174
|
+
await container_1.appService.updateApp(appId, {
|
|
175
|
+
name: config.appName,
|
|
176
|
+
redirect_uris: redirectUrls,
|
|
177
|
+
scopes: nextScopes,
|
|
178
|
+
...(config.logoUri ? { logo_uri: config.logoUri } : {}),
|
|
179
|
+
});
|
|
180
|
+
spinner.stop();
|
|
181
|
+
if (config.appName)
|
|
182
|
+
(0, config_1.saveAppName)(appId, config.appName);
|
|
183
|
+
reportUpdateResult({
|
|
184
|
+
appId,
|
|
185
|
+
name: config.appName,
|
|
186
|
+
redirectUris: redirectUrls,
|
|
187
|
+
scopes: nextScopes,
|
|
188
|
+
logoUri: config.logoUri,
|
|
189
|
+
}, options.json);
|
|
190
|
+
}
|
|
191
|
+
async function resolveExistingState(appId, config, shouldWriteBack, silent) {
|
|
199
192
|
const configRedirectUrls = config?.auth?.redirectUrls;
|
|
200
193
|
const hasUsableConfigRedirectUrls = Array.isArray(configRedirectUrls) && configRedirectUrls.length > 0;
|
|
201
194
|
if (config && shouldWriteBack && hasUsableConfigRedirectUrls) {
|
|
202
195
|
// Use config as baseline only when it can safely preserve redirect URLs
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
196
|
+
return {
|
|
197
|
+
name: config.appName,
|
|
198
|
+
redirectUrls: configRedirectUrls,
|
|
199
|
+
logoUri: config.logoUri,
|
|
200
|
+
scopes: config.auth?.scopes ?? [],
|
|
201
|
+
};
|
|
207
202
|
}
|
|
208
|
-
|
|
203
|
+
if (config && shouldWriteBack) {
|
|
209
204
|
// Config matches the app, but missing/empty redirect URLs would otherwise clear
|
|
210
205
|
// remote redirect URIs on a name-only update. Fall back to the API for preservation.
|
|
211
|
-
const app = await fetchExistingApp(appId,
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
const app = await fetchExistingApp(appId, options.json);
|
|
219
|
-
existingName = app.name;
|
|
220
|
-
existingRedirectUrls = app.redirect_uris ?? [];
|
|
221
|
-
existingLogoUri = app.logo_uri;
|
|
222
|
-
existingScopes = app.scopes ?? [];
|
|
206
|
+
const app = await fetchExistingApp(appId, silent);
|
|
207
|
+
return {
|
|
208
|
+
name: config.appName ?? app.name,
|
|
209
|
+
redirectUrls: app.redirect_uris ?? [],
|
|
210
|
+
logoUri: config.logoUri ?? app.logo_uri,
|
|
211
|
+
scopes: config.auth?.scopes ?? app.scopes ?? [],
|
|
212
|
+
};
|
|
223
213
|
}
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
214
|
+
const app = await fetchExistingApp(appId, silent);
|
|
215
|
+
return {
|
|
216
|
+
name: app.name,
|
|
217
|
+
redirectUrls: app.redirect_uris ?? [],
|
|
218
|
+
logoUri: app.logo_uri,
|
|
219
|
+
scopes: app.scopes ?? [],
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
function appendUnique(existing, additions) {
|
|
223
|
+
const merged = [...existing];
|
|
224
|
+
for (const item of additions) {
|
|
225
|
+
if (!merged.includes(item)) {
|
|
226
|
+
merged.push(item);
|
|
231
227
|
}
|
|
232
228
|
}
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
229
|
+
return merged;
|
|
230
|
+
}
|
|
231
|
+
// Passing --scope signals migration intent: drop the deprecated legacy 'all'
|
|
232
|
+
// scope from the merge baseline so the outgoing payload is clean (BEX-214).
|
|
233
|
+
function mergeScopes(existingScopes, appendedScopes) {
|
|
234
|
+
const hasScopeFlag = appendedScopes.length > 0;
|
|
235
|
+
const migratingLegacyScopes = hasScopeFlag && (0, validators_1.containsLegacyAllScope)(existingScopes);
|
|
236
|
+
const baseline = migratingLegacyScopes
|
|
237
|
+
? existingScopes.filter((s) => s !== constants_1.LEGACY_ALL_SCOPE)
|
|
238
|
+
: existingScopes;
|
|
239
|
+
const mergedScopes = appendUnique(baseline, appendedScopes);
|
|
240
|
+
// Block any outgoing payload that still carries 'all' — either no --scope
|
|
241
|
+
// was passed (no migration intent), or 'all' was explicitly re-added.
|
|
242
|
+
if ((0, validators_1.containsLegacyAllScope)(mergedScopes)) {
|
|
243
|
+
throw new errors_1.CliError(en_1.messages.LEGACY_ALL_SCOPE_DEPRECATED_BLOCK);
|
|
244
|
+
}
|
|
245
|
+
return { mergedScopes, migratingLegacyScopes };
|
|
246
|
+
}
|
|
247
|
+
// Write back to app-config.json if appropriate
|
|
248
|
+
function writeBackProjectConfig(config, shouldWriteBack, options, mergedUrls, mergedScopes) {
|
|
249
|
+
if (!shouldWriteBack || !config) {
|
|
250
|
+
return false;
|
|
240
251
|
}
|
|
252
|
+
const updatedConfig = { ...config };
|
|
253
|
+
if (options.name) {
|
|
254
|
+
updatedConfig.appName = options.name;
|
|
255
|
+
}
|
|
256
|
+
if (options.logoUri) {
|
|
257
|
+
updatedConfig.logoUri = options.logoUri;
|
|
258
|
+
}
|
|
259
|
+
updatedConfig.auth = {
|
|
260
|
+
...updatedConfig.auth,
|
|
261
|
+
redirectUrls: mergedUrls,
|
|
262
|
+
scopes: mergedScopes,
|
|
263
|
+
};
|
|
264
|
+
(0, config_1.writeProjectConfig)(updatedConfig);
|
|
265
|
+
return true;
|
|
266
|
+
}
|
|
267
|
+
// Flags provided: merge with existing values
|
|
268
|
+
async function updateWithFlags(appId, config, shouldWriteBack, options) {
|
|
269
|
+
const existing = await resolveExistingState(appId, config, shouldWriteBack, options.json);
|
|
270
|
+
// Merge: --name wins, --redirect-uri appends (deduplicated), --logo-uri wins
|
|
271
|
+
const finalName = options.name ?? existing.name;
|
|
272
|
+
const mergedUrls = appendUnique(existing.redirectUrls, options.redirectUri ?? []);
|
|
273
|
+
const finalLogoUri = options.logoUri ?? existing.logoUri;
|
|
274
|
+
const { mergedScopes, migratingLegacyScopes } = mergeScopes(existing.scopes, options.scope ?? []);
|
|
241
275
|
const hasRedirectUriFlag = options.redirectUri !== undefined;
|
|
242
276
|
if (hasRedirectUriFlag && mergedUrls.length === 0) {
|
|
243
277
|
throw new errors_1.CliError(en_1.messages.APP_UPDATE_NO_REDIRECT_URLS);
|
|
@@ -249,30 +283,22 @@ exports.updateCommand = (0, command_handler_1.withCommandHandler)(async (options
|
|
|
249
283
|
if (!options.json) {
|
|
250
284
|
renderUpdateSummary({
|
|
251
285
|
appId,
|
|
252
|
-
currentName:
|
|
286
|
+
currentName: existing.name,
|
|
253
287
|
nextName: finalName,
|
|
254
|
-
currentUrls:
|
|
288
|
+
currentUrls: existing.redirectUrls,
|
|
255
289
|
nextUrls: mergedUrls,
|
|
256
|
-
currentLogoUri:
|
|
290
|
+
currentLogoUri: existing.logoUri,
|
|
257
291
|
nextLogoUri: finalLogoUri,
|
|
258
|
-
currentScopes:
|
|
292
|
+
currentScopes: existing.scopes,
|
|
259
293
|
nextScopes: mergedScopes,
|
|
294
|
+
migratingLegacyScopes,
|
|
260
295
|
});
|
|
261
296
|
}
|
|
262
297
|
if (!options.json && !options.yes) {
|
|
263
298
|
if (!process.stdin.isTTY) {
|
|
264
|
-
throw new errors_1.CliError(
|
|
299
|
+
throw new errors_1.CliError(NON_INTERACTIVE_CONFIRM_ERROR);
|
|
265
300
|
}
|
|
266
|
-
|
|
267
|
-
{
|
|
268
|
-
type: 'confirm',
|
|
269
|
-
name: 'confirmed',
|
|
270
|
-
message: en_1.messages.APP_UPDATE_CONFIRM,
|
|
271
|
-
default: true,
|
|
272
|
-
},
|
|
273
|
-
]);
|
|
274
|
-
if (!confirmed) {
|
|
275
|
-
(0, logger_1.logInfo)(`\n ${en_1.messages.APP_UPDATE_CANCELLED}\n`);
|
|
301
|
+
if (!(await confirmUpdate())) {
|
|
276
302
|
return;
|
|
277
303
|
}
|
|
278
304
|
}
|
|
@@ -286,45 +312,49 @@ exports.updateCommand = (0, command_handler_1.withCommandHandler)(async (options
|
|
|
286
312
|
spinner.stop();
|
|
287
313
|
if (finalName)
|
|
288
314
|
(0, config_1.saveAppName)(appId, finalName);
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
if (
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
scopes: mergedScopes,
|
|
311
|
-
...(finalLogoUri ? { logo_uri: finalLogoUri } : {}),
|
|
312
|
-
});
|
|
313
|
-
return;
|
|
314
|
-
}
|
|
315
|
-
(0, logger_1.logSuccess)(en_1.messages.APP_UPDATE_SUCCESS);
|
|
316
|
-
if (finalName) {
|
|
317
|
-
(0, logger_1.logInfo)(` Name: ${finalName}`);
|
|
315
|
+
const configWrittenBack = writeBackProjectConfig(config, shouldWriteBack, options, mergedUrls, mergedScopes);
|
|
316
|
+
reportUpdateResult({
|
|
317
|
+
appId,
|
|
318
|
+
name: finalName,
|
|
319
|
+
redirectUris: mergedUrls,
|
|
320
|
+
scopes: mergedScopes,
|
|
321
|
+
logoUri: finalLogoUri,
|
|
322
|
+
configWrittenBack,
|
|
323
|
+
}, options.json);
|
|
324
|
+
}
|
|
325
|
+
exports.updateCommand = (0, command_handler_1.withCommandHandler)(async (options) => {
|
|
326
|
+
const config = (0, config_1.readProjectConfig)();
|
|
327
|
+
const hasFlags = !!(options.name !== undefined ||
|
|
328
|
+
(options.redirectUri && options.redirectUri.length > 0) ||
|
|
329
|
+
options.logoUri !== undefined ||
|
|
330
|
+
(options.scope && options.scope.length > 0));
|
|
331
|
+
// Validate --name if provided (even empty strings)
|
|
332
|
+
if (options.name !== undefined) {
|
|
333
|
+
const nameCheck = (0, validators_1.validateAppName)(options.name);
|
|
334
|
+
if (nameCheck !== true)
|
|
335
|
+
throw new errors_1.CliError(nameCheck);
|
|
318
336
|
}
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
337
|
+
// readProjectConfig normalizes appId (trim + coerce finite numbers, reject
|
|
338
|
+
// empty) so `config.appId` is a guaranteed non-empty string whenever `config`
|
|
339
|
+
// is non-null.
|
|
340
|
+
const configAppId = config?.appId;
|
|
341
|
+
const configAppIdValid = !!configAppId;
|
|
342
|
+
// app-config.json is only usable when it describes the app being updated.
|
|
343
|
+
// When --app-id differs from config.appId, the local file belongs to a
|
|
344
|
+
// different app and must not be used as the update payload or merge base.
|
|
345
|
+
const configMatches = configAppIdValid && (!options.appId || options.appId === configAppId);
|
|
346
|
+
if (!hasFlags && !configMatches) {
|
|
347
|
+
throwNoUpdateSource(config, configAppIdValid, configAppId, options.appId);
|
|
323
348
|
}
|
|
324
|
-
|
|
325
|
-
|
|
349
|
+
const appId = resolveAppId(options.appId, configAppId, config);
|
|
350
|
+
// Write back only when config describes the app being updated.
|
|
351
|
+
const shouldWriteBack = configMatches;
|
|
352
|
+
if (!hasFlags) {
|
|
353
|
+
// config is guaranteed non-null here — the !hasFlags && !configMatches case throws above
|
|
354
|
+
await pushFullConfig(appId, config, options);
|
|
355
|
+
return;
|
|
326
356
|
}
|
|
327
|
-
|
|
357
|
+
await updateWithFlags(appId, config, shouldWriteBack, options);
|
|
328
358
|
});
|
|
329
359
|
async function fetchExistingApp(appId, silent) {
|
|
330
360
|
const spinner = (0, ui_1.createSpinner)('Fetching app...', { silent });
|
|
@@ -340,59 +370,60 @@ async function fetchExistingApp(appId, silent) {
|
|
|
340
370
|
}
|
|
341
371
|
return app;
|
|
342
372
|
}
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
const
|
|
347
|
-
const
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
(0, logger_1.logInfo)(` App ID: ${appId}`);
|
|
352
|
-
if (nextName) {
|
|
353
|
-
if (currentName && currentName !== nextName) {
|
|
354
|
-
(0, logger_1.logInfo)(` Name: ${currentName} → ${nextName}`);
|
|
355
|
-
}
|
|
356
|
-
else {
|
|
357
|
-
(0, logger_1.logInfo)(` Name: ${nextName}`);
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
const lines = [
|
|
361
|
-
...nextUrls.map((u) => (addedSet.has(u) ? `${u} (new)` : u)),
|
|
362
|
-
...removed.map((u) => `${u} (removed)`),
|
|
373
|
+
// Diff `current` vs `next`: next values keep their order (tagged `(new)` when
|
|
374
|
+
// absent from current), values dropped from current trail with `(removed)`.
|
|
375
|
+
function diffLines(current, next) {
|
|
376
|
+
const currentSet = new Set(current);
|
|
377
|
+
const nextSet = new Set(next);
|
|
378
|
+
return [
|
|
379
|
+
...next.map((v) => (currentSet.has(v) ? v : `${v} (new)`)),
|
|
380
|
+
...current.filter((v) => !nextSet.has(v)).map((v) => `${v} (removed)`),
|
|
363
381
|
];
|
|
382
|
+
}
|
|
383
|
+
// Print a labelled block; continuation lines are indented to align under the
|
|
384
|
+
// first value.
|
|
385
|
+
function logAligned(label, lines) {
|
|
364
386
|
lines.forEach((line, i) => {
|
|
365
|
-
|
|
366
|
-
(0, logger_1.logInfo)(`${prefix}${line}`);
|
|
387
|
+
(0, logger_1.logInfo)(`${i === 0 ? label : ' '}${line}`);
|
|
367
388
|
});
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
const removedScopes = (currentScopes ?? []).filter((s) => !nextScopesSet.has(s));
|
|
373
|
-
const scopeLines = nextScopes.length === 0 && removedScopes.length === 0
|
|
374
|
-
? ['(none)']
|
|
375
|
-
: [
|
|
376
|
-
...nextScopes.map((s) => (addedScopes.has(s) ? `${s} (new)` : s)),
|
|
377
|
-
...removedScopes.map((s) => `${s} (removed)`),
|
|
378
|
-
];
|
|
379
|
-
scopeLines.forEach((line, i) => {
|
|
380
|
-
const prefix = i === 0 ? ' Scopes: ' : ' ';
|
|
381
|
-
(0, logger_1.logInfo)(`${prefix}${line}`);
|
|
382
|
-
});
|
|
389
|
+
}
|
|
390
|
+
function renderNameLine(currentName, nextName) {
|
|
391
|
+
if (!nextName) {
|
|
392
|
+
return;
|
|
383
393
|
}
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
394
|
+
const renamePrefix = currentName && currentName !== nextName ? `${currentName} → ` : '';
|
|
395
|
+
(0, logger_1.logInfo)(` Name: ${renamePrefix}${nextName}`);
|
|
396
|
+
}
|
|
397
|
+
function renderScopeLines(currentScopes, nextScopes) {
|
|
398
|
+
if (nextScopes === undefined) {
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
const lines = diffLines(currentScopes ?? [], nextScopes);
|
|
402
|
+
logAligned(' Scopes: ', lines.length > 0 ? lines : ['(none)']);
|
|
403
|
+
}
|
|
404
|
+
function renderLogoLine(currentLogoUri, nextLogoUri) {
|
|
405
|
+
const label = ' Logo URL: ';
|
|
406
|
+
if (currentLogoUri && nextLogoUri && currentLogoUri !== nextLogoUri) {
|
|
407
|
+
(0, logger_1.logInfo)(`${label}${currentLogoUri} → ${nextLogoUri}`);
|
|
408
|
+
}
|
|
409
|
+
else if (nextLogoUri) {
|
|
410
|
+
(0, logger_1.logInfo)(`${label}${nextLogoUri}`);
|
|
411
|
+
}
|
|
412
|
+
else if (currentLogoUri) {
|
|
413
|
+
(0, logger_1.logInfo)(`${label}${currentLogoUri} (unchanged)`);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
function renderUpdateSummary(params) {
|
|
417
|
+
(0, logger_1.logInfo)('');
|
|
418
|
+
(0, logger_1.logInfo)(` ${en_1.messages.APP_UPDATE_SUMMARY}`);
|
|
419
|
+
(0, logger_1.logInfo)(` App ID: ${params.appId}`);
|
|
420
|
+
renderNameLine(params.currentName, params.nextName);
|
|
421
|
+
logAligned(' Redirect URLs: ', diffLines(params.currentUrls, params.nextUrls));
|
|
422
|
+
if (params.migratingLegacyScopes) {
|
|
423
|
+
(0, logger_1.logInfo)(` ${en_1.messages.LEGACY_ALL_SCOPE_UPDATE_MIGRATING}`);
|
|
395
424
|
}
|
|
425
|
+
renderScopeLines(params.currentScopes, params.nextScopes);
|
|
426
|
+
renderLogoLine(params.currentLogoUri, params.nextLogoUri);
|
|
396
427
|
(0, logger_1.logInfo)('');
|
|
397
428
|
}
|
|
398
429
|
function validateRedirectUrls(urls) {
|