Skillshub adobe-migration-deep-dive
install
source · Clone the upstream repo
git clone https://github.com/ComeOnOliver/skillshub
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/ComeOnOliver/skillshub "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/jeremylongshore/claude-code-plugins-plus-skills/adobe-migration-deep-dive" ~/.claude/skills/comeonoliver-skillshub-adobe-migration-deep-dive && rm -rf "$T"
manifest:
skills/jeremylongshore/claude-code-plugins-plus-skills/adobe-migration-deep-dive/SKILL.mdsource content
Adobe Migration Deep Dive
Overview
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.
Prerequisites
- Current system documentation with API inventory
- Adobe Developer Console project with target APIs
- Feature flag infrastructure
- Rollback strategy tested in staging
Instructions
Migration Type Assessment
| 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 |
Scenario 1: Consolidate to Firefly Services SDK
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);
Scenario 2: Migrate from Competitor to Adobe PDF Services
// 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(); }
Scenario 3: Image API Migration (Cloudinary to Firefly/Photoshop)
// 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; } // ... }
Phase-Based Migration Plan
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
Post-Migration Validation
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 }; }
Output
- Adapter layer abstracting old and new implementations
- Feature-flag controlled traffic split
- Phase-based migration with rollback at each stage
- Validation suite confirming migration success
Error Handling
| 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 |
Resources
Next Steps
For advanced troubleshooting, see
adobe-advanced-troubleshooting.