UNPKG

@swell/cli

Version:

Swell's command line interface/utility

160 lines (158 loc) 6.48 kB
import { Args, Flags } from '@oclif/core'; import { FetchError } from 'node-fetch'; import { default as localConfig } from '../../lib/config.js'; import { SwellCommand } from '../../swell-command.js'; export default class Content extends SwellCommand { static summary = 'Inspect content view extensions deployed in your store.'; static description = ` View content view extensions available in your Swell store. Without a content ID, this command lists all available content views. With a content ID, it retrieves the configuration for that specific content view in JSON format. `; static args = { 'content-id': Args.string({ description: 'Optional content view ID. Example: custom.admin.products, app.myapp.products', required: false, }), }; static flags = { live: Flags.boolean({ description: 'Use the live environment instead of test (default: test).', default: false, }), yes: Flags.boolean({ char: 'y', description: 'Accept all prompts automatically (for agent compatibility; no functional effect).', default: false, }), }; static examples = [ 'swell inspect content', 'swell inspect content custom.admin.products', 'swell inspect content app.honest_reviews.reviews', 'swell inspect content app.myapp.products --live', ]; async run() { const { args, flags } = await this.parse(Content); const { 'content-id': contentId } = args; const { live } = flags; if (!live) { await this.api.setEnv('test'); } // Detail mode: show configuration for a specific content view if (contentId) { await this.showContentDetail(contentId); return; } const store = localConfig.getDefaultStore(); // List mode: show all content views await this.listContent(store, live); } async catch(error) { if (error instanceof FetchError) { const message = `Could not connect to Swell API. Please try again later: ${error.message}`; return this.error(message, { exit: 2, code: error.code }); } return this.error(error.message, { exit: 1 }); } async listContent(store, live) { const { results: apps } = await this.api.get({ adminPath: '/apps', }); const appsById = Object.fromEntries(apps.map((app) => [app.id, app])); const { results: contentViews } = await this.api.getAll({ adminPath: '/data/:content', }); // Sort by resolved ID in reverse order (custom.* first) const sortedContentViews = [...contentViews].sort((a, b) => { const resolvedA = this.resolveContentId(a, appsById); const resolvedB = this.resolveContentId(b, appsById); return resolvedB.localeCompare(resolvedA); }); this.log(`Content views extensions available in '${store}' ${live ? '[live]' : '[test]'}:`); this.log(); this.showContentViews(sortedContentViews, appsById); // Show next step hints this.log(); this.log('Examples:'); this.log(' swell inspect content custom.admin.products'); this.log(' swell inspect content app.honest_reviews.accounts'); } showContentViews(contentViews, appsById) { const maxNameWidth = Math.max(...contentViews.map((cv) => cv.name.length), 15); for (const cv of contentViews) { this.showContentView(cv, maxNameWidth, appsById); } } showContentView(contentView, nameWidth, appsById) { const resolvedId = this.resolveContentId(contentView, appsById); const paddedName = contentView.name.padEnd(nameWidth + 2); this.log(`${paddedName}${resolvedId}`); } resolveContentId(contentView, appsById) { const { id, name } = contentView; // Parse ID: app.{appId}.{name} or custom.{source}.{name} const match = id.match(/^(app|custom)\.([^.]+)\.(.+)$/); if (!match) return id; // fallback to original const [, type, identifier] = match; if (type === 'custom') { return id; // custom IDs are already resolved } // type === 'app': resolve app ID to slug const appId = identifier; const app = appsById[appId]; if (!app) { return id; // fallback if app not found } const appSlug = this.getAppSlugId(app); return `app.${appSlug}.${name}`; } async showContentDetail(resolvedId) { // Reverse resolve to get unresolved ID for API const unresolvedId = await this.reverseResolveContentId(resolvedId); // Fetch content view const content = await this.api.get({ adminPath: `/data/:content/${unresolvedId}`, }); if (!content) { this.throwContentNotFound(resolvedId); } this.showContent(content); } async reverseResolveContentId(resolvedId) { // Parse ID: app.{slug}.{name} or custom.{source}.{name} const match = resolvedId.match(/^(app|custom)\.([^.]+)\.(.+)$/); if (!match) { throw new Error(`Invalid content ID format: ${resolvedId}`); } const [, type, identifier, name] = match; if (type === 'custom') { return resolvedId; // custom IDs don't need reverse resolution } // Check if identifier is already an app ID (ObjectID format) if (/^[\da-f]{24}$/.test(identifier)) { return resolvedId; // already unresolved format } // type === 'app': resolve slug to app ID const appSlug = identifier; const app = await this.getAppBySlug(appSlug); return `app.${app.id}.${name}`; } async getAppBySlug(slug) { const app = await this.api.get({ adminPath: `/apps/${slug}` }); if (!app) { throw new Error(`App '${slug}' not found.\nList available content: swell inspect content`); } return app; } getAppSlugId(app) { return app.public_id || app.private_id.replace(/^_/, ''); } throwContentNotFound(id) { throw new Error(`No content view found for '${id}'.\nList available content: swell inspect content`); } showContent(content) { this.log(JSON.stringify(content, null, 2)); } }