From adobe-pack
Migrates from legacy Adobe APIs to Firefly Services, replaces competitor document/image APIs with Adobe using adapters, and updates auth to OAuth Server-to-Server. Includes TypeScript SDK examples.
npx claudepluginhub jeremylongshore/claude-code-plugins-plus-skills --plugin adobe-packThis skill is limited to using the following tools:
Comprehensive guide for three major migration scenarios: (1) legacy Adobe API consolidation into Firefly Services, (2) migrating from competitor document/image APIs to Adobe, and (3) JWT credential migration to OAuth Server-to-Server.
Guides Adobe SDK upgrades: JWT-to-OAuth Server-to-Server migration, PDF Services v3-to-v4, Photoshop API cutout v1-to-v2 endpoint changes, with code examples and steps.
Guides Canva Connect API migrations from design platforms using strangler fig pattern. Assesses assets, maps APIs, and implements phased adapter strategy.
Implements low-downtime API migrations between versions or frameworks using strangler fig, traffic shadowing, endpoint mapping, adapters, and phased cutovers.
Share bugs, ideas, or general feedback.
Comprehensive guide for three major migration scenarios: (1) legacy Adobe API consolidation into Firefly Services, (2) migrating from competitor document/image APIs to Adobe, and (3) JWT credential migration to OAuth Server-to-Server.
| Type | From | To | Complexity | Duration |
|---|---|---|---|---|
| Auth migration | JWT credentials | OAuth Server-to-Server | Low | 1-2 days |
| API consolidation | Separate PS/LR endpoints | Firefly Services SDK | Medium | 1-2 weeks |
| Competitor replacement | Cloudinary/imgix/PDFTron | Adobe APIs | High | 4-8 weeks |
| Full replatform | Custom pipeline | Adobe App Builder | High | 2-3 months |
The Photoshop and Lightroom APIs were previously separate. They are now part of Firefly Services with a unified SDK:
// BEFORE: Separate clients for each API
import { PhotoshopAPI } from 'some-old-photoshop-client';
import { LightroomAPI } from 'some-old-lightroom-client';
// AFTER: Unified Firefly Services SDK
import { PhotoshopClient } from '@adobe/photoshop-apis';
import { LightroomClient } from '@adobe/lightroom-apis';
import { FireflyClient } from '@adobe/firefly-apis';
// All use the same OAuth credentials
const config = {
clientId: process.env.ADOBE_CLIENT_ID!,
accessToken: await getAccessToken(),
};
const photoshop = new PhotoshopClient(config);
const lightroom = new LightroomClient(config);
const firefly = new FireflyClient(config);
// src/adapters/document-adapter.ts
// Adapter pattern for gradual migration from PDFTron/other to Adobe
interface DocumentAdapter {
extractText(pdfPath: string): Promise<string>;
createPdf(htmlContent: string): Promise<Buffer>;
mergePdfs(pdfPaths: string[]): Promise<Buffer>;
}
// Old implementation
class PdfTronAdapter implements DocumentAdapter {
async extractText(pdfPath: string): Promise<string> {
// ... existing PDFTron code
}
// ...
}
// New Adobe implementation
class AdobePdfAdapter implements DocumentAdapter {
private pdfServices: PDFServices;
constructor() {
const credentials = new ServicePrincipalCredentials({
clientId: process.env.ADOBE_CLIENT_ID!,
clientSecret: process.env.ADOBE_CLIENT_SECRET!,
});
this.pdfServices = new PDFServices({ credentials });
}
async extractText(pdfPath: string): Promise<string> {
const inputStream = fs.createReadStream(pdfPath);
const inputAsset = await this.pdfServices.upload({
readStream: inputStream,
mimeType: MimeType.PDF,
});
const params = new ExtractPDFParams({
elementsToExtract: [ExtractElementType.TEXT],
});
const job = new ExtractPDFJob({ inputAsset, params });
const pollingURL = await this.pdfServices.submit({ job });
const result = await this.pdfServices.getJobResult({
pollingURL,
resultType: ExtractPDFResult,
});
// Parse structuredData.json from result ZIP
const streamAsset = await this.pdfServices.getContent({
asset: result.result!.resource,
});
// ... extract text from ZIP
return extractedText;
}
// ... implement createPdf, mergePdfs
}
// Feature-flag controlled routing
function getDocumentAdapter(): DocumentAdapter {
const adobePercentage = getFeatureFlag('adobe_pdf_migration_pct');
if (Math.random() * 100 < adobePercentage) {
return new AdobePdfAdapter();
}
return new PdfTronAdapter();
}
// src/adapters/image-adapter.ts
interface ImageAdapter {
removeBackground(inputUrl: string): Promise<string>;
resize(inputUrl: string, width: number, height: number): Promise<string>;
generateImage(prompt: string): Promise<string>;
}
class CloudinaryAdapter implements ImageAdapter {
async removeBackground(inputUrl: string): Promise<string> {
// ... existing Cloudinary code
return cloudinary.url(publicId, { effect: 'background_removal' });
}
// ...
}
class AdobeImageAdapter implements ImageAdapter {
async removeBackground(inputUrl: string): Promise<string> {
const token = await getAccessToken();
const outputUrl = await generatePresignedUploadUrl();
const response = await fetch('https://image.adobe.io/v2/remove-background', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'x-api-key': process.env.ADOBE_CLIENT_ID!,
'Content-Type': 'application/json',
},
body: JSON.stringify({
input: { href: inputUrl, storage: 'external' },
output: { href: outputUrl, storage: 'external', type: 'image/png' },
}),
});
const job = await response.json();
const result = await pollAdobeJob(job._links.self.href);
return outputUrl;
}
async generateImage(prompt: string): Promise<string> {
const token = await getAccessToken();
const response = await fetch('https://firefly-api.adobe.io/v3/images/generate', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'x-api-key': process.env.ADOBE_CLIENT_ID!,
'Content-Type': 'application/json',
},
body: JSON.stringify({ prompt, n: 1, size: { width: 1024, height: 1024 } }),
});
const result = await response.json();
return result.outputs[0].image.url;
}
// ...
}
Week 1-2: Setup
├── Create Adobe Developer Console project
├── Install SDKs and implement adapter layer
├── Write integration tests for both old and new
└── Deploy adapter with 0% traffic to Adobe
Week 3-4: Validation
├── Route 5% traffic to Adobe adapter
├── Compare results (output quality, latency, error rate)
├── Fix edge cases discovered in production traffic
└── Increase to 25% if metrics are acceptable
Week 5-6: Gradual Migration
├── Increase to 50% traffic
├── Monitor cost impact (Adobe vs old provider)
├── Address any performance regressions
└── Increase to 100% if all metrics pass
Week 7-8: Cleanup
├── Remove old adapter code
├── Delete old provider credentials
├── Update documentation
└── Run postmortem on migration
async function validateMigration(): Promise<{ passed: boolean; checks: any[] }> {
const checks = [
{ name: 'Auth working', fn: async () => !!(await getAccessToken()) },
{ name: 'PDF extract works', fn: async () => {
const result = await adobeAdapter.extractText('./test/fixture.pdf');
return result.length > 0;
}},
{ name: 'Image generation works', fn: async () => {
const url = await adobeAdapter.generateImage('test blue square');
return url.startsWith('https://');
}},
{ name: 'Error rate < 1%', fn: async () => {
const metrics = await getErrorRate('adobe', '1h');
return metrics < 0.01;
}},
];
const results = await Promise.all(
checks.map(async c => ({ name: c.name, passed: await c.fn() }))
);
return { passed: results.every(r => r.passed), checks: results };
}
| Issue | Cause | Solution |
|---|---|---|
| Quality difference | Different rendering engines | Compare side-by-side; tune parameters |
| Higher latency | Adobe async APIs | Use parallel job submission |
| Cost increase | Different pricing model | Implement caching; optimize batch sizes |
| Missing features | Not all features map 1:1 | Document gaps; find Adobe alternatives |
For advanced troubleshooting, see adobe-advanced-troubleshooting.