UNPKG

@swell/cli

Version:

Swell's command line interface/utility

126 lines (122 loc) 5.68 kB
import { InspectResourceCommand, inspectResourceBaseArgs, inspectResourceBaseFlags, } from '../../inspect-resource-command.js'; import { isObjectId } from '../../lib/apps/object-id.js'; import { buildAppSlugMap, resolveAppId } from '../../lib/apps/resolve.js'; import { buildNotificationTemplate, expandModelCandidates, formatDisambiguationError, formatNotificationKey, groupNotificationRecord, parseNotificationKey, } from '../../lib/inspect/notifications.js'; export default class Notifications extends InspectResourceCommand { static summary = 'Notifications across apps and platform.'; static description = `Lists notifications across all apps plus platform 'com.*' system records, grouped by owner. Pass --app=<slug> or --app=. (current swell.json) to scope. App keys include the model (app.<slug>.<model>.<name>) since identity is (api, model, name, app_id?) — two notifications in one app can share a name on different models. Own-app prefix apps/<own>/... is stripped in display. Pass an identifier to view a single record as JSON. Identifier forms: com.<model>.<name> — platform record app.<app>.<model>.<name> — app record (full key) app.<app>.<name> — legacy; ambiguous → errors with candidates <name> — requires --app= scope <24-char id> — any scope `; static args = { ...inspectResourceBaseArgs }; static flags = { ...inspectResourceBaseFlags }; static examples = [ 'swell inspect notifications', 'swell inspect notifications --app=my-app', 'swell inspect notifications com.orders.receipt.v2', 'swell inspect notifications app.notify_me.subscriptions.back-in-stock', 'swell inspect notifications back-in-stock --app=notify_me', 'swell inspect notifications --live', ]; resourceLabel = 'Notifications'; resourceLabelSingular = 'notification'; adminPath = '/data/:notifications'; commandName = 'notifications'; async run() { const { args, flags } = await this.parse(Notifications); await this.runInspect({ args, flags }); } keyFor(record, appSlugById) { return formatNotificationKey(record, appSlugById); } groupForRecord(record, appSlugById) { return groupNotificationRecord(record, appSlugById); } metaFor(r) { return r.enabled === false ? 'disabled' : undefined; } hints(record) { const template = buildNotificationTemplate(record); if (!template) { return []; } return [ `swell api get '/notifications?where[template]=${template}&limit=10'`, ]; } async showDetail(identifier, scope, flags) { const parsed = parseNotificationKey(identifier); let record = null; switch (parsed.kind) { case 'hex': case 'system_id': { record = await this.api.get({ adminPath: `${this.adminPath}/${parsed.id}`, }); break; } case 'app_full': { const appId = await this.resolveSlugOrHex(parsed.slug); const candidates = await expandModelCandidates(parsed.model, appId, (s) => resolveAppId(this.api, s)); record = await this.lookupTriplet(appId, candidates, parsed.name); break; } case 'app_short': { const appId = await this.resolveSlugOrHex(parsed.slug); record = await this.lookupAndDisambiguate(appId, parsed.name, identifier); break; } case 'bare': { if (!scope.appId) { this.error(`Bare ${this.resourceLabelSingular} name '${identifier}' requires --app=<slug> or --app=. to scope. ` + `Alternatively, pass a full key (app.<app>.<model>.<name> or com.<model>.<name>).`, { exit: 1 }); } record = await this.lookupAndDisambiguate(scope.appId, parsed.name, identifier); break; } case 'invalid': { this.error(`Invalid ${this.resourceLabelSingular} identifier '${identifier}'. ` + `Expected bare name, app.<app>.<model>.<name>, com.<model>.<name>, or 24-char id.`, { exit: 1 }); } } if (!record) { this.throwNotFound(identifier); } this.emitDetail(record, flags); } async resolveSlugOrHex(slugOrHex) { return isObjectId(slugOrHex) ? slugOrHex : resolveAppId(this.api, slugOrHex); } async lookupTriplet(appId, modelCandidates, name) { const response = await this.api.get({ adminPath: this.adminPath }, { query: { app_id: appId, model: { $in: modelCandidates }, name, limit: 1, }, }); return response?.results?.[0] ?? null; } async lookupAndDisambiguate(appId, name, identifier) { // Fetch one past the display cap so the formatter can distinguish // "exactly 10 candidates" from "10+ candidates and we're truncating". const response = await this.api.get({ adminPath: this.adminPath }, { query: { app_id: appId, name, limit: 11 } }); const results = response?.results ?? []; if (results.length <= 1) { return results[0] ?? null; } const appSlugById = await buildAppSlugMap(this.api); this.error(formatDisambiguationError(results, appSlugById, identifier), { exit: 1, }); } }