@getbrevo/cli 0.0.1-alpha.1 → 1.0.0-alpha.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 +26 -6
- package/README.md +51 -5
- package/agent-context/AGENTS.md +59 -0
- package/agent-context/SKILL.md +44 -0
- package/dist/api/client.d.ts.map +1 -1
- package/dist/api/client.js +8 -5
- package/dist/api/client.js.map +1 -1
- package/dist/bin/index.js +33 -11
- package/dist/bin/index.js.map +1 -1
- package/dist/commands/app/create.d.ts.map +1 -1
- package/dist/commands/app/create.js +16 -8
- package/dist/commands/app/create.js.map +1 -1
- package/dist/commands/app/credentials.d.ts.map +1 -1
- package/dist/commands/app/credentials.js +9 -3
- package/dist/commands/app/credentials.js.map +1 -1
- package/dist/commands/app/delete.d.ts.map +1 -1
- package/dist/commands/app/delete.js +1 -0
- 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 +18 -1
- 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 +4 -0
- package/dist/commands/app/scaffold.js.map +1 -1
- package/dist/commands/app/start.d.ts.map +1 -1
- package/dist/commands/app/start.js +88 -3
- 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 +9 -5
- package/dist/commands/app/update.js.map +1 -1
- package/dist/commands/definitions.js +8 -8
- package/dist/commands/definitions.js.map +1 -1
- package/dist/commands/login.d.ts.map +1 -1
- package/dist/commands/login.js +4 -0
- package/dist/commands/login.js.map +1 -1
- package/dist/lang/en.d.ts +15 -14
- package/dist/lang/en.d.ts.map +1 -1
- package/dist/lang/en.js +19 -18
- package/dist/lang/en.js.map +1 -1
- package/dist/lib/auth-guard.d.ts.map +1 -1
- package/dist/lib/auth-guard.js +7 -0
- package/dist/lib/auth-guard.js.map +1 -1
- package/dist/lib/config.d.ts +5 -0
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +82 -0
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/constants.d.ts +1 -0
- package/dist/lib/constants.d.ts.map +1 -1
- package/dist/lib/constants.js +2 -1
- package/dist/lib/constants.js.map +1 -1
- package/dist/lib/min-version-check.d.ts +15 -0
- package/dist/lib/min-version-check.d.ts.map +1 -0
- package/dist/lib/min-version-check.js +33 -0
- package/dist/lib/min-version-check.js.map +1 -0
- package/dist/lib/port.d.ts +14 -1
- package/dist/lib/port.d.ts.map +1 -1
- package/dist/lib/port.js +29 -5
- package/dist/lib/port.js.map +1 -1
- package/dist/lib/update-notifier.d.ts +34 -0
- package/dist/lib/update-notifier.d.ts.map +1 -0
- package/dist/lib/update-notifier.js +255 -0
- package/dist/lib/update-notifier.js.map +1 -0
- package/dist/lib/validators.d.ts +1 -1
- package/dist/lib/validators.d.ts.map +1 -1
- package/dist/lib/validators.js +8 -13
- package/dist/lib/validators.js.map +1 -1
- package/dist/services/app.d.ts.map +1 -1
- package/dist/services/app.js +26 -3
- package/dist/services/app.js.map +1 -1
- package/dist/templates/files/AGENTS.md.tmpl +18 -12
- package/dist/templates/files/CLAUDE.md.tmpl +22 -13
- package/dist/templates/files/README.md.tmpl +10 -4
- package/dist/templates/files/app-config.json.tmpl +2 -0
- package/dist/templates/files/src/oauth/handler.js.tmpl +3 -0
- package/dist/templates/files/src/oauth/server.js.tmpl +1 -1
- package/package.json +3 -3
- package/dist/commands/app/test.d.ts +0 -6
- package/dist/commands/app/test.d.ts.map +0 -1
- package/dist/commands/app/test.js +0 -495
- package/dist/commands/app/test.js.map +0 -1
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.shouldShowBannerBefore = shouldShowBannerBefore;
|
|
37
|
+
exports.shouldSkipCheck = shouldSkipCheck;
|
|
38
|
+
exports.compareVersions = compareVersions;
|
|
39
|
+
exports.isNewer = isNewer;
|
|
40
|
+
exports.readCache = readCache;
|
|
41
|
+
exports.writeCache = writeCache;
|
|
42
|
+
exports.fetchLatestVersion = fetchLatestVersion;
|
|
43
|
+
exports.formatBanner = formatBanner;
|
|
44
|
+
exports.startUpdateCheck = startUpdateCheck;
|
|
45
|
+
exports.notifyUpdate = notifyUpdate;
|
|
46
|
+
const fs = __importStar(require("fs"));
|
|
47
|
+
const os = __importStar(require("os"));
|
|
48
|
+
const path = __importStar(require("path"));
|
|
49
|
+
const en_1 = require("../lang/en");
|
|
50
|
+
const REGISTRY_URL = (name) => `https://registry.npmjs.org/${encodeURIComponent(name).replace('%40', '@')}/latest`;
|
|
51
|
+
const TTL_MS = 24 * 60 * 60 * 1000;
|
|
52
|
+
const FETCH_TIMEOUT_MS = 2000;
|
|
53
|
+
const NOTIFY_WAIT_MS = 1500;
|
|
54
|
+
const CACHE_FILE = 'update-check.json';
|
|
55
|
+
function getCachePath(override, env = process.env) {
|
|
56
|
+
if (override)
|
|
57
|
+
return override;
|
|
58
|
+
const dir = env.BREVO_CONFIG_HOME || path.join(os.homedir(), '.brevo');
|
|
59
|
+
return path.join(dir, CACHE_FILE);
|
|
60
|
+
}
|
|
61
|
+
// True when the banner must print before parseAsync runs, either because
|
|
62
|
+
// Commander exits synchronously (bare `brevo`, --help, --version) and would
|
|
63
|
+
// bypass the post-run notify, or because the command starts a long interactive
|
|
64
|
+
// flow where users should see the upgrade up front.
|
|
65
|
+
function shouldShowBannerBefore(argv) {
|
|
66
|
+
const args = argv.slice(2);
|
|
67
|
+
if (args.length === 0)
|
|
68
|
+
return true;
|
|
69
|
+
if (args.includes('--help') || args.includes('-h'))
|
|
70
|
+
return true;
|
|
71
|
+
if (args.includes('--version') || args.includes('-V'))
|
|
72
|
+
return true;
|
|
73
|
+
return args[0] === 'app' && (args[1] === 'init' || args[1] === 'create');
|
|
74
|
+
}
|
|
75
|
+
function shouldSkipCheck(opts) {
|
|
76
|
+
const env = opts.env ?? process.env;
|
|
77
|
+
const argv = opts.argv ?? process.argv;
|
|
78
|
+
const isTTY = opts.isTTY ?? Boolean(process.stdout.isTTY);
|
|
79
|
+
if (env.CI === 'true' || env.CI === '1')
|
|
80
|
+
return true;
|
|
81
|
+
if (!isTTY)
|
|
82
|
+
return true;
|
|
83
|
+
if (env.NO_UPDATE_NOTIFIER === '1' || env.NO_UPDATE_NOTIFIER === 'true')
|
|
84
|
+
return true;
|
|
85
|
+
if (env.BREVO_NO_UPDATE_NOTIFIER === '1' || env.BREVO_NO_UPDATE_NOTIFIER === 'true')
|
|
86
|
+
return true;
|
|
87
|
+
if (argv.includes('--no-update-notifier'))
|
|
88
|
+
return true;
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
function parseVersion(v) {
|
|
92
|
+
const match = /^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?/.exec(v.trim());
|
|
93
|
+
if (!match || !match[1] || !match[2] || !match[3])
|
|
94
|
+
return undefined;
|
|
95
|
+
return {
|
|
96
|
+
major: parseInt(match[1], 10),
|
|
97
|
+
minor: parseInt(match[2], 10),
|
|
98
|
+
patch: parseInt(match[3], 10),
|
|
99
|
+
prerelease: match[4] ?? '',
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
// Per semver §11.4: split on '.', compare identifiers; numeric identifiers
|
|
103
|
+
// are compared numerically and always rank below non-numeric ones; a longer
|
|
104
|
+
// prerelease set outranks a shorter one when the leading identifiers match.
|
|
105
|
+
function comparePrerelease(a, b) {
|
|
106
|
+
if (a === b)
|
|
107
|
+
return 0;
|
|
108
|
+
const aParts = a.split('.');
|
|
109
|
+
const bParts = b.split('.');
|
|
110
|
+
const len = Math.min(aParts.length, bParts.length);
|
|
111
|
+
for (let i = 0; i < len; i++) {
|
|
112
|
+
const ai = aParts[i] ?? '';
|
|
113
|
+
const bi = bParts[i] ?? '';
|
|
114
|
+
if (ai === bi)
|
|
115
|
+
continue;
|
|
116
|
+
const aNum = /^\d+$/.test(ai);
|
|
117
|
+
const bNum = /^\d+$/.test(bi);
|
|
118
|
+
if (aNum && bNum) {
|
|
119
|
+
const diff = parseInt(ai, 10) - parseInt(bi, 10);
|
|
120
|
+
if (diff !== 0)
|
|
121
|
+
return diff > 0 ? 1 : -1;
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
if (aNum)
|
|
125
|
+
return -1;
|
|
126
|
+
if (bNum)
|
|
127
|
+
return 1;
|
|
128
|
+
return ai > bi ? 1 : -1;
|
|
129
|
+
}
|
|
130
|
+
if (aParts.length === bParts.length)
|
|
131
|
+
return 0;
|
|
132
|
+
return aParts.length > bParts.length ? 1 : -1;
|
|
133
|
+
}
|
|
134
|
+
function compareVersions(current, latest) {
|
|
135
|
+
const c = parseVersion(current);
|
|
136
|
+
const l = parseVersion(latest);
|
|
137
|
+
if (!c || !l)
|
|
138
|
+
return 0;
|
|
139
|
+
if (l.major !== c.major)
|
|
140
|
+
return l.major - c.major;
|
|
141
|
+
if (l.minor !== c.minor)
|
|
142
|
+
return l.minor - c.minor;
|
|
143
|
+
if (l.patch !== c.patch)
|
|
144
|
+
return l.patch - c.patch;
|
|
145
|
+
if (c.prerelease && !l.prerelease)
|
|
146
|
+
return 1;
|
|
147
|
+
if (!c.prerelease && l.prerelease)
|
|
148
|
+
return -1;
|
|
149
|
+
return comparePrerelease(l.prerelease, c.prerelease);
|
|
150
|
+
}
|
|
151
|
+
function isNewer(current, latest) {
|
|
152
|
+
return compareVersions(current, latest) > 0;
|
|
153
|
+
}
|
|
154
|
+
function readCache(cachePath) {
|
|
155
|
+
try {
|
|
156
|
+
const raw = JSON.parse(fs.readFileSync(cachePath, 'utf-8'));
|
|
157
|
+
if (raw &&
|
|
158
|
+
typeof raw === 'object' &&
|
|
159
|
+
typeof raw.latest === 'string' &&
|
|
160
|
+
typeof raw.lastChecked === 'number' &&
|
|
161
|
+
Number.isFinite(raw.lastChecked)) {
|
|
162
|
+
return { latest: raw.latest, lastChecked: raw.lastChecked };
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
catch {
|
|
166
|
+
// missing or corrupt — caller treats as no cache
|
|
167
|
+
}
|
|
168
|
+
return undefined;
|
|
169
|
+
}
|
|
170
|
+
function writeCache(cachePath, cache) {
|
|
171
|
+
try {
|
|
172
|
+
fs.mkdirSync(path.dirname(cachePath), { recursive: true, mode: 0o700 });
|
|
173
|
+
fs.writeFileSync(cachePath, JSON.stringify(cache, null, 2), 'utf-8');
|
|
174
|
+
}
|
|
175
|
+
catch {
|
|
176
|
+
// non-fatal — banner still works from in-memory value
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
async function fetchLatestVersion(name, opts = { pkg: { name, version: '0.0.0' } }) {
|
|
180
|
+
const fetchImpl = opts.fetchImpl ?? fetch;
|
|
181
|
+
const timeoutMs = opts.fetchTimeoutMs ?? FETCH_TIMEOUT_MS;
|
|
182
|
+
const controller = new AbortController();
|
|
183
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
184
|
+
try {
|
|
185
|
+
const res = await fetchImpl(REGISTRY_URL(name), {
|
|
186
|
+
signal: controller.signal,
|
|
187
|
+
headers: { Accept: 'application/json' },
|
|
188
|
+
});
|
|
189
|
+
if (!res.ok)
|
|
190
|
+
return undefined;
|
|
191
|
+
const json = (await res.json());
|
|
192
|
+
return typeof json.version === 'string' ? json.version : undefined;
|
|
193
|
+
}
|
|
194
|
+
catch {
|
|
195
|
+
return undefined;
|
|
196
|
+
}
|
|
197
|
+
finally {
|
|
198
|
+
clearTimeout(timer);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
function formatBanner(current, latest, name) {
|
|
202
|
+
const line1 = en_1.messages.UPDATE_AVAILABLE(current, latest);
|
|
203
|
+
const line2 = en_1.messages.UPDATE_RUN(name);
|
|
204
|
+
const line3 = en_1.messages.UPDATE_RUN_YARN(name);
|
|
205
|
+
const inner = Math.max(line1.length, line2.length, line3.length) + 4;
|
|
206
|
+
const top = '╭' + '─'.repeat(inner) + '╮';
|
|
207
|
+
const bot = '╰' + '─'.repeat(inner) + '╯';
|
|
208
|
+
const pad = (s) => ' ' + s + ' '.repeat(inner - s.length - 2);
|
|
209
|
+
return [
|
|
210
|
+
'',
|
|
211
|
+
` ${top}`,
|
|
212
|
+
` │${pad(line1)}│`,
|
|
213
|
+
` │${pad(line2)}│`,
|
|
214
|
+
` │${pad(line3)}│`,
|
|
215
|
+
` ${bot}`,
|
|
216
|
+
'',
|
|
217
|
+
].join('\n');
|
|
218
|
+
}
|
|
219
|
+
function startUpdateCheck(opts) {
|
|
220
|
+
if (shouldSkipCheck(opts)) {
|
|
221
|
+
return { pending: Promise.resolve() };
|
|
222
|
+
}
|
|
223
|
+
const cachePath = getCachePath(opts.cachePath, opts.env);
|
|
224
|
+
const now = opts.now ? opts.now() : Date.now();
|
|
225
|
+
const ttl = opts.ttlMs ?? TTL_MS;
|
|
226
|
+
const cache = readCache(cachePath);
|
|
227
|
+
const stale = !cache || now - cache.lastChecked > ttl;
|
|
228
|
+
if (!stale) {
|
|
229
|
+
return { cachedLatest: cache?.latest, pending: Promise.resolve() };
|
|
230
|
+
}
|
|
231
|
+
const handle = {
|
|
232
|
+
cachedLatest: cache?.latest,
|
|
233
|
+
pending: Promise.resolve(),
|
|
234
|
+
};
|
|
235
|
+
handle.pending = (async () => {
|
|
236
|
+
const latest = await fetchLatestVersion(opts.pkg.name, opts);
|
|
237
|
+
if (latest) {
|
|
238
|
+
// Prefer the freshly fetched version so first-run users (no cache)
|
|
239
|
+
// and stale-cache users see the banner without waiting another run.
|
|
240
|
+
handle.cachedLatest = latest;
|
|
241
|
+
writeCache(cachePath, { latest, lastChecked: now });
|
|
242
|
+
}
|
|
243
|
+
})();
|
|
244
|
+
return handle;
|
|
245
|
+
}
|
|
246
|
+
async function notifyUpdate(handle, pkg, output = process.stderr, waitMs = NOTIFY_WAIT_MS) {
|
|
247
|
+
await Promise.race([
|
|
248
|
+
handle.pending,
|
|
249
|
+
new Promise((resolve) => setTimeout(resolve, waitMs).unref?.()),
|
|
250
|
+
]);
|
|
251
|
+
if (handle.cachedLatest && isNewer(pkg.version, handle.cachedLatest)) {
|
|
252
|
+
output.write(formatBanner(pkg.version, handle.cachedLatest, pkg.name) + '\n');
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
//# sourceMappingURL=update-notifier.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"update-notifier.js","sourceRoot":"","sources":["../../src/lib/update-notifier.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8CA,wDAMC;AAED,0CAYC;AA+CD,0CAUC;AAED,0BAEC;AAED,8BAgBC;AAED,gCAOC;AAED,gDAqBC;AAED,oCAiBC;AAOD,4CA8BC;AAED,oCAcC;AAzPD,uCAAyB;AACzB,uCAAyB;AACzB,2CAA6B;AAC7B,mCAAsC;AAEtC,MAAM,YAAY,GAAG,CAAC,IAAY,EAAU,EAAE,CAC5C,8BAA8B,kBAAkB,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,SAAS,CAAC;AAEtF,MAAM,MAAM,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;AACnC,MAAM,gBAAgB,GAAG,IAAI,CAAC;AAC9B,MAAM,cAAc,GAAG,IAAI,CAAC;AAE5B,MAAM,UAAU,GAAG,mBAAmB,CAAC;AAwBvC,SAAS,YAAY,CAAC,QAAiB,EAAE,MAAyB,OAAO,CAAC,GAAG;IAC3E,IAAI,QAAQ;QAAE,OAAO,QAAQ,CAAC;IAC9B,MAAM,GAAG,GAAG,GAAG,CAAC,iBAAiB,IAAI,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,QAAQ,CAAC,CAAC;IACvE,OAAO,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC;AACpC,CAAC;AAED,yEAAyE;AACzE,4EAA4E;AAC5E,+EAA+E;AAC/E,oDAAoD;AACpD,SAAgB,sBAAsB,CAAC,IAAuB;IAC5D,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAC3B,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACnC,IAAI,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IAChE,IAAI,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IACnE,OAAO,IAAI,CAAC,CAAC,CAAC,KAAK,KAAK,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,MAAM,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,QAAQ,CAAC,CAAC;AAC3E,CAAC;AAED,SAAgB,eAAe,CAAC,IAA2B;IACzD,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,CAAC;IACpC,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IACvC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,IAAI,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAE1D,IAAI,GAAG,CAAC,EAAE,KAAK,MAAM,IAAI,GAAG,CAAC,EAAE,KAAK,GAAG;QAAE,OAAO,IAAI,CAAC;IACrD,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IACxB,IAAI,GAAG,CAAC,kBAAkB,KAAK,GAAG,IAAI,GAAG,CAAC,kBAAkB,KAAK,MAAM;QAAE,OAAO,IAAI,CAAC;IACrF,IAAI,GAAG,CAAC,wBAAwB,KAAK,GAAG,IAAI,GAAG,CAAC,wBAAwB,KAAK,MAAM;QAAE,OAAO,IAAI,CAAC;IACjG,IAAI,IAAI,CAAC,QAAQ,CAAC,sBAAsB,CAAC;QAAE,OAAO,IAAI,CAAC;IAEvD,OAAO,KAAK,CAAC;AACf,CAAC;AASD,SAAS,YAAY,CAAC,CAAS;IAC7B,MAAM,KAAK,GAAG,8CAA8C,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;IAC5E,IAAI,CAAC,KAAK,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;QAAE,OAAO,SAAS,CAAC;IACpE,OAAO;QACL,KAAK,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;QAC7B,KAAK,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;QAC7B,KAAK,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;QAC7B,UAAU,EAAE,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE;KAC3B,CAAC;AACJ,CAAC;AAED,2EAA2E;AAC3E,4EAA4E;AAC5E,4EAA4E;AAC5E,SAAS,iBAAiB,CAAC,CAAS,EAAE,CAAS;IAC7C,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,CAAC,CAAC;IACtB,MAAM,MAAM,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC5B,MAAM,MAAM,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC5B,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;IACnD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC;QAC7B,MAAM,EAAE,GAAG,MAAM,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QAC3B,MAAM,EAAE,GAAG,MAAM,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QAC3B,IAAI,EAAE,KAAK,EAAE;YAAE,SAAS;QACxB,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAC9B,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAC9B,IAAI,IAAI,IAAI,IAAI,EAAE,CAAC;YACjB,MAAM,IAAI,GAAG,QAAQ,CAAC,EAAE,EAAE,EAAE,CAAC,GAAG,QAAQ,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;YACjD,IAAI,IAAI,KAAK,CAAC;gBAAE,OAAO,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YACzC,SAAS;QACX,CAAC;QACD,IAAI,IAAI;YAAE,OAAO,CAAC,CAAC,CAAC;QACpB,IAAI,IAAI;YAAE,OAAO,CAAC,CAAC;QACnB,OAAO,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC1B,CAAC;IACD,IAAI,MAAM,CAAC,MAAM,KAAK,MAAM,CAAC,MAAM;QAAE,OAAO,CAAC,CAAC;IAC9C,OAAO,MAAM,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAChD,CAAC;AAED,SAAgB,eAAe,CAAC,OAAe,EAAE,MAAc;IAC7D,MAAM,CAAC,GAAG,YAAY,CAAC,OAAO,CAAC,CAAC;IAChC,MAAM,CAAC,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC;IAC/B,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC;QAAE,OAAO,CAAC,CAAC;IACvB,IAAI,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,KAAK;QAAE,OAAO,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC;IAClD,IAAI,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,KAAK;QAAE,OAAO,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC;IAClD,IAAI,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,KAAK;QAAE,OAAO,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC;IAClD,IAAI,CAAC,CAAC,UAAU,IAAI,CAAC,CAAC,CAAC,UAAU;QAAE,OAAO,CAAC,CAAC;IAC5C,IAAI,CAAC,CAAC,CAAC,UAAU,IAAI,CAAC,CAAC,UAAU;QAAE,OAAO,CAAC,CAAC,CAAC;IAC7C,OAAO,iBAAiB,CAAC,CAAC,CAAC,UAAU,EAAE,CAAC,CAAC,UAAU,CAAC,CAAC;AACvD,CAAC;AAED,SAAgB,OAAO,CAAC,OAAe,EAAE,MAAc;IACrD,OAAO,eAAe,CAAC,OAAO,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC;AAC9C,CAAC;AAED,SAAgB,SAAS,CAAC,SAAiB;IACzC,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC,CAAC;QAC5D,IACE,GAAG;YACH,OAAO,GAAG,KAAK,QAAQ;YACvB,OAAO,GAAG,CAAC,MAAM,KAAK,QAAQ;YAC9B,OAAO,GAAG,CAAC,WAAW,KAAK,QAAQ;YACnC,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,WAAW,CAAC,EAChC,CAAC;YACD,OAAO,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,EAAE,GAAG,CAAC,WAAW,EAAE,CAAC;QAC9D,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,iDAAiD;IACnD,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,SAAgB,UAAU,CAAC,SAAiB,EAAE,KAAuB;IACnE,IAAI,CAAC;QACH,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;QACxE,EAAE,CAAC,aAAa,CAAC,SAAS,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;IACvE,CAAC;IAAC,MAAM,CAAC;QACP,sDAAsD;IACxD,CAAC;AACH,CAAC;AAEM,KAAK,UAAU,kBAAkB,CACtC,IAAY,EACZ,OAA8B,EAAE,GAAG,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE;IAEjE,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,IAAI,KAAK,CAAC;IAC1C,MAAM,SAAS,GAAG,IAAI,CAAC,cAAc,IAAI,gBAAgB,CAAC;IAC1D,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;IACzC,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,EAAE,SAAS,CAAC,CAAC;IAC9D,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,SAAS,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE;YAC9C,MAAM,EAAE,UAAU,CAAC,MAAM;YACzB,OAAO,EAAE,EAAE,MAAM,EAAE,kBAAkB,EAAE;SACxC,CAAC,CAAC;QACH,IAAI,CAAC,GAAG,CAAC,EAAE;YAAE,OAAO,SAAS,CAAC;QAC9B,MAAM,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAA0B,CAAC;QACzD,OAAO,OAAO,IAAI,CAAC,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC;IACrE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;YAAS,CAAC;QACT,YAAY,CAAC,KAAK,CAAC,CAAC;IACtB,CAAC;AACH,CAAC;AAED,SAAgB,YAAY,CAAC,OAAe,EAAE,MAAc,EAAE,IAAY;IACxE,MAAM,KAAK,GAAG,aAAQ,CAAC,gBAAgB,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IACzD,MAAM,KAAK,GAAG,aAAQ,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;IACxC,MAAM,KAAK,GAAG,aAAQ,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC;IAC7C,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IACrE,MAAM,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC;IAC1C,MAAM,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC;IAC1C,MAAM,GAAG,GAAG,CAAC,CAAS,EAAU,EAAE,CAAC,IAAI,GAAG,CAAC,GAAG,GAAG,CAAC,MAAM,CAAC,KAAK,GAAG,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAC/E,OAAO;QACL,EAAE;QACF,KAAK,GAAG,EAAE;QACV,MAAM,GAAG,CAAC,KAAK,CAAC,GAAG;QACnB,MAAM,GAAG,CAAC,KAAK,CAAC,GAAG;QACnB,MAAM,GAAG,CAAC,KAAK,CAAC,GAAG;QACnB,KAAK,GAAG,EAAE;QACV,EAAE;KACH,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC;AAOD,SAAgB,gBAAgB,CAAC,IAA2B;IAC1D,IAAI,eAAe,CAAC,IAAI,CAAC,EAAE,CAAC;QAC1B,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC;IACxC,CAAC;IAED,MAAM,SAAS,GAAG,YAAY,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC;IACzD,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC;IAC/C,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,IAAI,MAAM,CAAC;IACjC,MAAM,KAAK,GAAG,SAAS,CAAC,SAAS,CAAC,CAAC;IAEnC,MAAM,KAAK,GAAG,CAAC,KAAK,IAAI,GAAG,GAAG,KAAK,CAAC,WAAW,GAAG,GAAG,CAAC;IACtD,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC;IACrE,CAAC;IAED,MAAM,MAAM,GAAsB;QAChC,YAAY,EAAE,KAAK,EAAE,MAAM;QAC3B,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE;KAC3B,CAAC;IACF,MAAM,CAAC,OAAO,GAAG,CAAC,KAAK,IAAI,EAAE;QAC3B,MAAM,MAAM,GAAG,MAAM,kBAAkB,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QAC7D,IAAI,MAAM,EAAE,CAAC;YACX,mEAAmE;YACnE,oEAAoE;YACpE,MAAM,CAAC,YAAY,GAAG,MAAM,CAAC;YAC7B,UAAU,CAAC,SAAS,EAAE,EAAE,MAAM,EAAE,WAAW,EAAE,GAAG,EAAE,CAAC,CAAC;QACtD,CAAC;IACH,CAAC,CAAC,EAAE,CAAC;IAEL,OAAO,MAAM,CAAC;AAChB,CAAC;AAEM,KAAK,UAAU,YAAY,CAChC,MAAyB,EACzB,GAAY,EACZ,SAA6B,OAAO,CAAC,MAAM,EAC3C,SAAiB,cAAc;IAE/B,MAAM,OAAO,CAAC,IAAI,CAAC;QACjB,MAAM,CAAC,OAAO;QACd,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC;KACtE,CAAC,CAAC;IAEH,IAAI,MAAM,CAAC,YAAY,IAAI,OAAO,CAAC,GAAG,CAAC,OAAO,EAAE,MAAM,CAAC,YAAY,CAAC,EAAE,CAAC;QACrE,MAAM,CAAC,KAAK,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,EAAE,MAAM,CAAC,YAAY,EAAE,GAAG,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,CAAC;IAChF,CAAC;AACH,CAAC"}
|
package/dist/lib/validators.d.ts
CHANGED
|
@@ -15,7 +15,7 @@ export declare function validateEnum(value: string | undefined, allowed: readonl
|
|
|
15
15
|
*/
|
|
16
16
|
export declare function validateUrl(value: string | undefined, fieldName: string): void;
|
|
17
17
|
/**
|
|
18
|
-
* Commander.js collect function for repeatable --redirect-
|
|
18
|
+
* Commander.js collect function for repeatable --redirect-uri flags.
|
|
19
19
|
* Validates each URL and accumulates into an array.
|
|
20
20
|
*/
|
|
21
21
|
export declare function collectUrls(value: string, previous?: string[]): string[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validators.d.ts","sourceRoot":"","sources":["../../src/lib/validators.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"validators.d.ts","sourceRoot":"","sources":["../../src/lib/validators.ts"],"names":[],"mappings":"AAKA;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAU3D;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAC1B,KAAK,EAAE,MAAM,GAAG,SAAS,EACzB,OAAO,EAAE,SAAS,MAAM,EAAE,EAC1B,QAAQ,EAAE,MAAM,GACf,IAAI,CAIN;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,CAkB9E;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,GAAE,MAAM,EAAO,GAAG,MAAM,EAAE,CAG5E;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAMxE;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAMhD"}
|
package/dist/lib/validators.js
CHANGED
|
@@ -7,7 +7,6 @@ exports.collectUrls = collectUrls;
|
|
|
7
7
|
exports.parsePositiveInt = parsePositiveInt;
|
|
8
8
|
exports.parseAppId = parseAppId;
|
|
9
9
|
const errors_1 = require("./errors");
|
|
10
|
-
const logger_1 = require("./logger");
|
|
11
10
|
const APP_NAME_MAX_LENGTH = 48;
|
|
12
11
|
const APP_NAME_REGEX = /^[a-zA-Z0-9 ._\-\u00C0-\u024F]+$/;
|
|
13
12
|
/**
|
|
@@ -19,10 +18,12 @@ function validateAppName(name) {
|
|
|
19
18
|
const trimmed = name.trim();
|
|
20
19
|
if (trimmed.length === 0)
|
|
21
20
|
return 'App name cannot be empty.';
|
|
22
|
-
if (trimmed.length > APP_NAME_MAX_LENGTH)
|
|
21
|
+
if (trimmed.length > APP_NAME_MAX_LENGTH) {
|
|
23
22
|
return `App name must be at most ${APP_NAME_MAX_LENGTH} characters (got ${trimmed.length}).`;
|
|
24
|
-
|
|
23
|
+
}
|
|
24
|
+
if (!APP_NAME_REGEX.test(trimmed)) {
|
|
25
25
|
return 'App name can only contain letters, numbers, spaces, hyphens, dots, underscores, and accented characters.';
|
|
26
|
+
}
|
|
26
27
|
return true;
|
|
27
28
|
}
|
|
28
29
|
/**
|
|
@@ -31,9 +32,7 @@ function validateAppName(name) {
|
|
|
31
32
|
*/
|
|
32
33
|
function validateEnum(value, allowed, flagName) {
|
|
33
34
|
if (value && !allowed.includes(value)) {
|
|
34
|
-
|
|
35
|
-
(0, logger_1.logInfo)(` ${msg}`);
|
|
36
|
-
throw new errors_1.CliError(msg);
|
|
35
|
+
throw new errors_1.CliError(`Invalid ${flagName} "${value}". Must be one of: ${allowed.join(', ')}.`);
|
|
37
36
|
}
|
|
38
37
|
}
|
|
39
38
|
/**
|
|
@@ -47,9 +46,7 @@ function validateUrl(value, fieldName) {
|
|
|
47
46
|
// spaces, so "http://a/cb, http://b/cb" parses as a single valid URL. That has let
|
|
48
47
|
// comma-separated values sneak into redirect_uris as one corrupted entry.
|
|
49
48
|
if (/[\s,]/.test(value)) {
|
|
50
|
-
|
|
51
|
-
(0, logger_1.logInfo)(` ${msg}`);
|
|
52
|
-
throw new errors_1.CliError(msg);
|
|
49
|
+
throw new errors_1.CliError(`Invalid ${fieldName}: "${value}" contains whitespace or a comma. Pass each URL with a separate --redirect-uri flag.`);
|
|
53
50
|
}
|
|
54
51
|
try {
|
|
55
52
|
const parsed = new URL(value);
|
|
@@ -58,13 +55,11 @@ function validateUrl(value, fieldName) {
|
|
|
58
55
|
}
|
|
59
56
|
}
|
|
60
57
|
catch {
|
|
61
|
-
|
|
62
|
-
(0, logger_1.logInfo)(` ${msg}`);
|
|
63
|
-
throw new errors_1.CliError(msg);
|
|
58
|
+
throw new errors_1.CliError(`Invalid ${fieldName}: "${value}" is not a valid HTTP/HTTPS URL.`);
|
|
64
59
|
}
|
|
65
60
|
}
|
|
66
61
|
/**
|
|
67
|
-
* Commander.js collect function for repeatable --redirect-
|
|
62
|
+
* Commander.js collect function for repeatable --redirect-uri flags.
|
|
68
63
|
* Validates each URL and accumulates into an array.
|
|
69
64
|
*/
|
|
70
65
|
function collectUrls(value, previous = []) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validators.js","sourceRoot":"","sources":["../../src/lib/validators.ts"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"validators.js","sourceRoot":"","sources":["../../src/lib/validators.ts"],"names":[],"mappings":";;AAUA,0CAUC;AAMD,oCAQC;AAMD,kCAkBC;AAMD,kCAGC;AAMD,4CAMC;AAMD,gCAMC;AA3FD,qCAAoC;AAEpC,MAAM,mBAAmB,GAAG,EAAE,CAAC;AAC/B,MAAM,cAAc,GAAG,kCAAkC,CAAC;AAE1D;;;;GAIG;AACH,SAAgB,eAAe,CAAC,IAAY;IAC1C,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;IAC5B,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,2BAA2B,CAAC;IAC7D,IAAI,OAAO,CAAC,MAAM,GAAG,mBAAmB,EAAE,CAAC;QACzC,OAAO,4BAA4B,mBAAmB,oBAAoB,OAAO,CAAC,MAAM,IAAI,CAAC;IAC/F,CAAC;IACD,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;QAClC,OAAO,0GAA0G,CAAC;IACpH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;GAGG;AACH,SAAgB,YAAY,CAC1B,KAAyB,EACzB,OAA0B,EAC1B,QAAgB;IAEhB,IAAI,KAAK,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QACtC,MAAM,IAAI,iBAAQ,CAAC,WAAW,QAAQ,KAAK,KAAK,sBAAsB,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC/F,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,SAAgB,WAAW,CAAC,KAAyB,EAAE,SAAiB;IACtE,IAAI,CAAC,KAAK;QAAE,OAAO;IACnB,kFAAkF;IAClF,mFAAmF;IACnF,0EAA0E;IAC1E,IAAI,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QACxB,MAAM,IAAI,iBAAQ,CAChB,WAAW,SAAS,MAAM,KAAK,sFAAsF,CACtH,CAAC;IACJ,CAAC;IACD,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC;QAC9B,IAAI,MAAM,CAAC,QAAQ,KAAK,OAAO,IAAI,MAAM,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;YAChE,MAAM,IAAI,KAAK,CAAC,cAAc,CAAC,CAAC;QAClC,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,iBAAQ,CAAC,WAAW,SAAS,MAAM,KAAK,kCAAkC,CAAC,CAAC;IACxF,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,SAAgB,WAAW,CAAC,KAAa,EAAE,WAAqB,EAAE;IAChE,WAAW,CAAC,KAAK,EAAE,cAAc,CAAC,CAAC;IACnC,OAAO,CAAC,GAAG,QAAQ,EAAE,KAAK,CAAC,CAAC;AAC9B,CAAC;AAED;;;GAGG;AACH,SAAgB,gBAAgB,CAAC,KAAa,EAAE,QAAgB;IAC9D,MAAM,CAAC,GAAG,MAAM,CAAC,QAAQ,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IACrC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QAClC,MAAM,IAAI,iBAAQ,CAAC,WAAW,QAAQ,MAAM,KAAK,8BAA8B,CAAC,CAAC;IACnF,CAAC;IACD,OAAO,CAAC,CAAC;AACX,CAAC;AAED;;;GAGG;AACH,SAAgB,UAAU,CAAC,KAAa;IACtC,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;IAC7B,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,MAAM,IAAI,iBAAQ,CAAC,0CAA0C,CAAC,CAAC;IACjE,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../../src/services/app.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAO1C,OAAO,EAAE,QAAQ,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../../src/services/app.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAO1C,OAAO,EAAE,QAAQ,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAmBvD,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,SAAS;yBAChB,OAAO,CAAC,QAAQ,EAAE,CAAC;oBAQ3B,MAAM,GAAG,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC;2BAWtC,MAAM,iBACN,CAAC,GAAG,EAAE,QAAQ,KAAK,MAAM,GACvC,OAAO,CAAC,MAAM,CAAC;IA2BlB;;;;OAIG;iCACgC,MAAM,GAAG,OAAO,CAAC;QAAE,GAAG,EAAE,QAAQ,CAAC;QAAC,KAAK,EAAE,MAAM,EAAE,CAAA;KAAE,GAAG,IAAI,CAAC;IA8B9F;;;;OAIG;8BACuB,MAAM,OAAO,QAAQ,GAAG,IAAI;uBAU7B;QACvB,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,EAAE,OAAO,CAAC;QAChB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;QACzB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;KACnB,GAAG,OAAO,CAAC,iBAAiB,CAAC;qBASrB,MAAM,QACP;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,aAAa,EAAE,MAAM,EAAE,CAAA;KAAE,GAC/C,OAAO,CAAC,IAAI,CAAC;qBAIO,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;EAQhD;AAED,MAAM,MAAM,UAAU,GAAG,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAC"}
|
package/dist/services/app.js
CHANGED
|
@@ -13,6 +13,12 @@ const ui_1 = require("../lib/ui");
|
|
|
13
13
|
const en_1 = require("../lang/en");
|
|
14
14
|
const config_1 = require("../lib/config");
|
|
15
15
|
const normalize_app_id_1 = require("./normalize-app-id");
|
|
16
|
+
function rethrowNotFound(err, appId) {
|
|
17
|
+
if (err instanceof errors_1.ApiError && err.statusCode === 404) {
|
|
18
|
+
throw new errors_1.CliError(`App ${appId} not found.`, err.exitCode);
|
|
19
|
+
}
|
|
20
|
+
throw err;
|
|
21
|
+
}
|
|
16
22
|
/**
|
|
17
23
|
* Check apps exist and throw with user-facing message if empty.
|
|
18
24
|
*/
|
|
@@ -28,7 +34,13 @@ function createAppService(client) {
|
|
|
28
34
|
return {
|
|
29
35
|
fetchAppsList,
|
|
30
36
|
async fetchApp(appId) {
|
|
31
|
-
|
|
37
|
+
let app;
|
|
38
|
+
try {
|
|
39
|
+
app = await client.get(constants_1.ENDPOINTS.OAUTH_APP(appId));
|
|
40
|
+
}
|
|
41
|
+
catch (err) {
|
|
42
|
+
rethrowNotFound(err, appId);
|
|
43
|
+
}
|
|
32
44
|
return app ? (0, normalize_app_id_1.normalizeAppId)(app) : null;
|
|
33
45
|
},
|
|
34
46
|
async pickApp(promptMessage, formatChoice) {
|
|
@@ -62,7 +74,13 @@ function createAppService(client) {
|
|
|
62
74
|
* so we fall back to the locally cached value.
|
|
63
75
|
*/
|
|
64
76
|
async resolveAppCredentials(appId) {
|
|
65
|
-
|
|
77
|
+
let raw;
|
|
78
|
+
try {
|
|
79
|
+
raw = await client.get(constants_1.ENDPOINTS.OAUTH_APP(appId));
|
|
80
|
+
}
|
|
81
|
+
catch (err) {
|
|
82
|
+
rethrowNotFound(err, appId);
|
|
83
|
+
}
|
|
66
84
|
if (!raw)
|
|
67
85
|
return null;
|
|
68
86
|
const app = (0, normalize_app_id_1.normalizeAppId)(raw);
|
|
@@ -110,7 +128,12 @@ function createAppService(client) {
|
|
|
110
128
|
await client.put(constants_1.ENDPOINTS.APP_STORE_APP_UPDATE(appId), body);
|
|
111
129
|
},
|
|
112
130
|
async deleteApp(appId) {
|
|
113
|
-
|
|
131
|
+
try {
|
|
132
|
+
await client.delete(constants_1.ENDPOINTS.OAUTH_APP(appId));
|
|
133
|
+
}
|
|
134
|
+
catch (err) {
|
|
135
|
+
rethrowNotFound(err, appId);
|
|
136
|
+
}
|
|
114
137
|
},
|
|
115
138
|
};
|
|
116
139
|
}
|
package/dist/services/app.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app.js","sourceRoot":"","sources":["../../src/services/app.ts"],"names":[],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"app.js","sourceRoot":"","sources":["../../src/services/app.ts"],"names":[],"mappings":"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"}
|
|
@@ -9,22 +9,28 @@ Implements the authorization code flow to obtain access tokens for the Brevo API
|
|
|
9
9
|
|
|
10
10
|
## Project structure
|
|
11
11
|
```
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
12
|
+
.
|
|
13
|
+
├── app-config.json App metadata (app ID, redirect URIs, scopes)
|
|
14
|
+
├── .gitignore Ignores .env.local and build artifacts
|
|
15
|
+
├── AGENTS.md AI agent guidance (this file)
|
|
16
|
+
├── CLAUDE.md Claude Code guidance
|
|
17
|
+
├── README.md Human-facing setup guide
|
|
18
|
+
└── src/
|
|
19
|
+
└── oauth/ Self-contained OAuth test feature
|
|
20
|
+
├── package.json Dependencies and scripts
|
|
21
|
+
├── server.js Test server — Express app + dotenv + routes
|
|
22
|
+
├── handler.js OAuth routes (/auth/login, /auth/callback, /auth/refresh)
|
|
23
|
+
├── token-store.js In-memory token management (swap for DB in production)
|
|
24
|
+
├── .env.example Credential template (safe to commit)
|
|
25
|
+
└── .env.local Local credentials (gitignored — never commit)
|
|
21
26
|
```
|
|
22
27
|
|
|
23
28
|
## OAuth server
|
|
24
29
|
- OAuth uses realm-based paths: `/realms/{realm}/oauth/...`
|
|
25
30
|
- Default realm: `partner`
|
|
26
31
|
- Token endpoint expects `application/x-www-form-urlencoded` body (not query params)
|
|
27
|
-
-
|
|
32
|
+
- Grant types used by this scaffold: `authorization_code` (callback) and `refresh_token` (`/auth/refresh`)
|
|
33
|
+
- Other grant types supported by the Brevo OAuth server but not wired into this scaffold: `client_credentials`
|
|
28
34
|
|
|
29
35
|
## OAuth flow
|
|
30
36
|
1. `/auth/login` — generates CSRF state, redirects user to `{OAUTH_BASE}/realms/{realm}/oauth/authorize`
|
|
@@ -35,7 +41,7 @@ app-config.json App metadata (app ID, redirect URIs, permissions)
|
|
|
35
41
|
|
|
36
42
|
## Development
|
|
37
43
|
```bash
|
|
38
|
-
yarn --cwd src/oauth
|
|
44
|
+
yarn --cwd src/oauth install
|
|
39
45
|
# or: npm --prefix src/oauth install
|
|
40
|
-
brevo app start oauth #
|
|
46
|
+
brevo app start oauth # port is read from app-config.json; override with --port
|
|
41
47
|
```
|
|
@@ -1,29 +1,34 @@
|
|
|
1
1
|
# CLAUDE.md — {{APP_NAME}}
|
|
2
2
|
|
|
3
3
|
## Project
|
|
4
|
-
Brevo OAuth integration for app `{{
|
|
4
|
+
Brevo OAuth integration for app `{{APP_ID}}`.
|
|
5
5
|
Scaffolded with the [Brevo CLI](https://github.com/getbrevo/brevo-cli).
|
|
6
6
|
|
|
7
7
|
> **This is a test app** scaffolded by the Brevo CLI to help you test your OAuth integration locally.
|
|
8
8
|
|
|
9
9
|
## Run
|
|
10
10
|
```bash
|
|
11
|
-
yarn --cwd src/oauth
|
|
11
|
+
yarn --cwd src/oauth install
|
|
12
12
|
# or: npm --prefix src/oauth install
|
|
13
|
-
brevo app start oauth #
|
|
13
|
+
brevo app start oauth # port is read from app-config.json; override with --port
|
|
14
14
|
```
|
|
15
15
|
|
|
16
16
|
## Structure
|
|
17
17
|
```
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
18
|
+
.
|
|
19
|
+
├── app-config.json App metadata (ID, scopes, redirect URLs)
|
|
20
|
+
├── .gitignore Ignores .env.local and build artifacts
|
|
21
|
+
├── AGENTS.md AI agent guidance
|
|
22
|
+
├── CLAUDE.md Claude Code guidance (this file)
|
|
23
|
+
├── README.md Human-facing setup guide
|
|
24
|
+
└── src/
|
|
25
|
+
└── oauth/ Self-contained OAuth test feature
|
|
26
|
+
├── package.json Dependencies and scripts
|
|
27
|
+
├── server.js Test server entry point
|
|
28
|
+
├── handler.js OAuth routes (/auth/login, /auth/callback, /auth/refresh)
|
|
29
|
+
├── token-store.js In-memory token store
|
|
30
|
+
├── .env.example Credential template (committed)
|
|
31
|
+
└── .env.local Local credentials (gitignored — never commit)
|
|
27
32
|
```
|
|
28
33
|
|
|
29
34
|
## OAuth server
|
|
@@ -47,4 +52,8 @@ app-config.json App metadata
|
|
|
47
52
|
- `/auth/refresh` — refreshes tokens using `refresh_token` grant
|
|
48
53
|
|
|
49
54
|
## Next steps
|
|
50
|
-
- Replace in-memory token store with a real database
|
|
55
|
+
- Replace the in-memory token store in `src/oauth/token-store.js` with a real database (the global singleton is process-local so data is lost on restart, and it is not session-isolated, so multiple users/sessions/tabs can read or overwrite the same in-memory tokens)
|
|
56
|
+
- Handle token-refresh failures — `/auth/refresh` currently returns a 500 on any error; surface 401 vs 5xx distinctly so callers can re-trigger `/auth/login` when the refresh token has expired
|
|
57
|
+
- Add `/auth/logout` that calls `POST /realms/{realm}/oauth/revoke` and clears the token store (the endpoint is supported by the OAuth server but the scaffold doesn't use it yet)
|
|
58
|
+
- Add automated tests around the callback and refresh routes (e.g. with `supertest` + axios mocks) before extending the flow
|
|
59
|
+
- Persist (or rotate) the CSRF `state` per session — the scaffold stores a single global state, which is fine for solo localhost testing but not for anything else
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# {{APP_NAME}}
|
|
2
2
|
|
|
3
|
-
Brevo OAuth integration scaffolded with the Brevo CLI.
|
|
3
|
+
Brevo OAuth integration for testing and implementing app authorization flows, scaffolded with the Brevo CLI.
|
|
4
4
|
|
|
5
5
|
> **This is a starter / test app** to help you exercise the OAuth flow locally. Treat it as a reference — it is not intended for production use.
|
|
6
6
|
|
|
@@ -27,7 +27,7 @@ Brevo OAuth integration scaffolded with the Brevo CLI.
|
|
|
27
27
|
```
|
|
28
28
|
.
|
|
29
29
|
├── app-config.json App config — ID, scopes, redirect URLs
|
|
30
|
-
├── .gitignore Ignores .env.local and build
|
|
30
|
+
├── .gitignore Ignores .env.local and build artifacts
|
|
31
31
|
├── AGENTS.md AI agent guidance
|
|
32
32
|
├── CLAUDE.md Claude Code guidance
|
|
33
33
|
├── README.md (this file)
|
|
@@ -35,7 +35,7 @@ Brevo OAuth integration scaffolded with the Brevo CLI.
|
|
|
35
35
|
└── oauth/ Self-contained OAuth test feature
|
|
36
36
|
├── package.json Dependencies & `yarn start` script
|
|
37
37
|
├── server.js Test server entry point
|
|
38
|
-
├── handler.js OAuth routes (/auth/login, /auth/callback)
|
|
38
|
+
├── handler.js OAuth routes (/auth/login, /auth/callback, /auth/refresh)
|
|
39
39
|
├── token-store.js In-memory token management
|
|
40
40
|
├── .env.example Credential template (committed)
|
|
41
41
|
└── .env.local Local credentials (gitignored, chmod 600)
|
|
@@ -63,7 +63,13 @@ brevo app update --redirect-url https://myapp.com/callback
|
|
|
63
63
|
brevo app update --name "New Name" --redirect-url https://myapp.com/callback
|
|
64
64
|
```
|
|
65
65
|
|
|
66
|
-
`--redirect-url` is repeatable and **appends** to existing URLs.
|
|
66
|
+
`--redirect-url` is repeatable and **appends** to existing URLs.
|
|
67
|
+
|
|
68
|
+
To avoid unintentionally accumulating stale redirect URLs over time:
|
|
69
|
+
|
|
70
|
+
- Periodically review your redirect list and remove obsolete entries in `app-config.json`.
|
|
71
|
+
- Run `brevo app update` **without flags** to replace the remote configuration with the local file.
|
|
72
|
+
- When flags are provided, the local `app-config.json` is updated automatically after a successful API call.
|
|
67
73
|
|
|
68
74
|
Outside this project directory, target an app by ID:
|
|
69
75
|
|
|
@@ -42,6 +42,7 @@ const CLIENT_SECRET = process.env.CLIENT_SECRET;
|
|
|
42
42
|
const REDIRECT_URI = process.env.REDIRECT_URI || '{{REDIRECT_URI}}';
|
|
43
43
|
const OAUTH_BASE = process.env.OAUTH_BASE || '{{OAUTH_BASE}}';
|
|
44
44
|
const OAUTH_REALM = process.env.OAUTH_REALM || '{{OAUTH_REALM}}';
|
|
45
|
+
const PORT = process.env.PORT || 3009;
|
|
45
46
|
|
|
46
47
|
const realmPath = `${OAUTH_BASE}/realms/${OAUTH_REALM}/oauth`;
|
|
47
48
|
|
|
@@ -112,6 +113,8 @@ router.get('/callback', async (req, res) => {
|
|
|
112
113
|
console.log(`\nAccess token received (expires in ${expires_in}s)`);
|
|
113
114
|
console.log('Refresh token received');
|
|
114
115
|
console.log('View tokens in your browser (masked — click to reveal).');
|
|
116
|
+
console.log(`Token refresh: http://localhost:${PORT}/auth/refresh`);
|
|
117
|
+
console.log('Next: use the access token to call the Brevo API, or open src/oauth/handler.js to see how the flow is wired.');
|
|
115
118
|
|
|
116
119
|
// Set no-cache headers — tokens are sensitive (local development only)
|
|
117
120
|
res.set('Cache-Control', 'no-store, no-cache, must-revalidate');
|
|
@@ -49,7 +49,7 @@ const AUTH_URL = `${process.env.OAUTH_BASE}/realms/${process.env.OAUTH_REALM}/oa
|
|
|
49
49
|
+ `?response_type=code`
|
|
50
50
|
+ `&client_id=${encodeURIComponent(process.env.CLIENT_ID)}`
|
|
51
51
|
+ `&redirect_uri=${encodeURIComponent(REDIRECT_URI)}`
|
|
52
|
-
+ `&state
|
|
52
|
+
+ `&state=[random]`
|
|
53
53
|
+ `&scope=${encodeURIComponent(SCOPES)}`;
|
|
54
54
|
|
|
55
55
|
app.use('/auth', authRouter);
|