aemdm 0.4.0 → 0.5.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/dist/cli.js CHANGED
@@ -1,6 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  import { fileURLToPath } from "node:url";
3
+ import { createRequire } from "node:module";
3
4
  import { realpathSync } from "node:fs";
5
+ const require = createRequire(import.meta.url);
6
+ const pkg = require("../package.json");
4
7
  import { Command, CommanderError, Option } from "commander";
5
8
  import { z } from "zod";
6
9
  import { CliError, normalizeBucket, request, requestJson, resolveBucket, resolveOptionalImsToken, resolveSearchAuth, } from "./lib/client.js";
@@ -36,6 +39,7 @@ const assetGetSchema = z
36
39
  output: z.string().optional(),
37
40
  metadata: z.boolean().default(false),
38
41
  imsToken: z.string().optional(),
42
+ mimeType: z.string().optional(),
39
43
  })
40
44
  .superRefine((value, ctx) => {
41
45
  if (value.metadata && value.output) {
@@ -190,6 +194,22 @@ async function handleAssetGet(assetId, options, runtime) {
190
194
  writeJson(runtime.stdout, metadata);
191
195
  return;
192
196
  }
197
+ let mimeType = parsed.mimeType;
198
+ if (!parsed.original && !parsed.format && !parsed.binary && !mimeType) {
199
+ if (imsToken) {
200
+ const metadata = await requestJson(buildMetadataUrl(baseUrl, assetId), {
201
+ imsToken,
202
+ fetchImpl: runtime.fetchImpl,
203
+ });
204
+ mimeType = metadata?.repositoryMetadata?.["dc:format"];
205
+ if (mimeType)
206
+ verbose(runtime, `detected mime type: ${mimeType}`);
207
+ }
208
+ else {
209
+ throw new CliError("Cannot determine asset type without authentication. " +
210
+ "Provide --format for images, --original for documents, or --ims-token to auto-detect.");
211
+ }
212
+ }
193
213
  const url = buildAssetUrl(baseUrl, {
194
214
  assetId,
195
215
  seoName: parsed.seoName,
@@ -199,6 +219,7 @@ async function handleAssetGet(assetId, options, runtime) {
199
219
  quality: parsed.quality,
200
220
  maxQuality: parsed.maxQuality,
201
221
  original: parsed.original,
222
+ mimeType,
202
223
  });
203
224
  if (!parsed.binary) {
204
225
  writeLine(runtime.stdout, url);
@@ -282,6 +303,7 @@ async function handleSearch(options, runtime) {
282
303
  return;
283
304
  }
284
305
  const dimensions = resolveDimensions(parsed.size, parsed.width, parsed.height);
306
+ const mimeType = firstHit.repositoryMetadata?.["dc:format"];
285
307
  const assetUrl = buildAssetUrl(baseUrl, {
286
308
  assetId,
287
309
  seoName: parsed.seoName,
@@ -291,6 +313,7 @@ async function handleSearch(options, runtime) {
291
313
  quality: parsed.quality,
292
314
  maxQuality: parsed.maxQuality,
293
315
  original: parsed.original,
316
+ mimeType,
294
317
  });
295
318
  if (parsed.firstUrl) {
296
319
  writeLine(runtime.stdout, assetUrl);
@@ -346,10 +369,37 @@ function buildProgram(runtime) {
346
369
  const program = new Command();
347
370
  program
348
371
  .name("aemdm")
349
- .description("CLI for Adobe Dynamic Media with OpenAPI")
350
- .version("0.4.0")
372
+ .description(`CLI for Adobe Dynamic Media with OpenAPI v${pkg.version}`)
373
+ .version(pkg.version)
351
374
  .showHelpAfterError()
352
375
  .option("-v, --verbose", "Show additional diagnostic output")
376
+ .addHelpText("after", `
377
+ Bucket:
378
+ Most commands need a delivery bucket. Provide it in one of three ways:
379
+ 1. --bucket delivery-p123-e456.adobeaemcloud.com (per-command flag)
380
+ 2. AEMDM_BUCKET environment variable
381
+ 3. Save a default: aemdm --bucket delivery-p123-e456.adobeaemcloud.com
382
+ This writes to ~/.aemdm/config.json and is used when no flag or env var is set.
383
+
384
+ Authentication:
385
+ Public asset URLs and basic metadata (HEAD-based) require no authentication.
386
+ Full metadata, binary downloads, and search require an IMS bearer token:
387
+ 1. --ims-token <token> (per-command flag)
388
+ 2. AEMDM_IMS_TOKEN environment variable
389
+ 3. Save a default: aemdm --ims-token <token>
390
+ This writes the token to ~/.aemdm/config.json for reuse.
391
+
392
+ Search also requires an Adobe API key:
393
+ 1. --api-key <key> (per-command flag)
394
+ 2. AEMDM_API_KEY environment variable
395
+
396
+ Examples:
397
+ aemdm --bucket delivery-p123-e456.adobeaemcloud.com Save default bucket
398
+ aemdm asset get urn:aaid:aem:1234 Print delivery URL
399
+ aemdm asset get urn:aaid:aem:1234 --format webp Print URL with format
400
+ aemdm asset get urn:aaid:aem:1234 --metadata Fetch asset metadata
401
+ aemdm search --text "hero banner" Search by text
402
+ aemdm search --text "logo" --first-url --format png Search and get URL`)
353
403
  .configureOutput({
354
404
  writeOut: (text) => runtime.stdout.write(text),
355
405
  writeErr: (text) => runtime.stderr.write(text),
@@ -366,7 +416,8 @@ function buildProgram(runtime) {
366
416
  .option("--output <file>", "Output file path for --binary. Use - for stdout")
367
417
  .option("--metadata", "Fetch metadata JSON instead of building a URL")
368
418
  .option("--ims-token <token>", "IMS bearer token for metadata or binary requests")
369
- .action((assetId, options) => handleAssetGet(assetId, options, runtime));
419
+ .option("--mime-type <type>", "Asset MIME type (skips metadata lookup for route detection)")
420
+ .action((assetId, options, cmd) => handleAssetGet(assetId, { ...cmd.parent.opts(), ...options }, runtime));
370
421
  configureCommonDeliveryOptions(program.command("search").description("Search assets and optionally resolve the first result"))
371
422
  .option("--bucket <bucket-or-url>", "Bucket host or full bucket URL")
372
423
  .option("--ims-token <token>", "IMS bearer token")
@@ -398,12 +449,39 @@ Core commands:
398
449
  - aemdm asset get <assetId>
399
450
  - aemdm search
400
451
 
452
+ Bucket configuration (pick one):
453
+ - --bucket delivery-p123-e456.adobeaemcloud.com (per-command flag)
454
+ - AEMDM_BUCKET environment variable
455
+ - Save a default: aemdm --bucket delivery-p123-e456.adobeaemcloud.com
456
+ This writes to ~/.aemdm/config.json and is used when no flag or env var is set.
457
+
458
+ Authentication:
459
+ - Public asset URLs and basic metadata (HEAD-based) require no authentication.
460
+ - Full metadata, binary downloads, and search require an IMS bearer token (pick one):
461
+ 1. --ims-token <token> (per-command flag)
462
+ 2. AEMDM_IMS_TOKEN environment variable
463
+ 3. Save a default: aemdm --ims-token <token>
464
+ This writes the token to ~/.aemdm/config.json for reuse.
465
+ - Both bucket and token can be saved together: aemdm --bucket <host> --ims-token <token>
466
+ - Search also requires an Adobe API key via --api-key <key> or AEMDM_API_KEY.
467
+
468
+ URL format:
469
+ - Images: https://<bucket>/adobe/assets/<assetId>/as/<seoName>.<format>
470
+ - Documents/video: https://<bucket>/adobe/assets/<assetId>/original/as/<seoName>
471
+ - Default seo-name is "asset", default format is "png".
472
+ - Use --format to override (gif, png, jpg, jpeg, webp, avif).
473
+ - Use --seo-name to set a custom SEO-friendly name segment.
474
+
475
+ Route detection:
476
+ - When authenticated, asset get auto-detects the MIME type via the metadata endpoint.
477
+ Images get the /as/<seoName>.<format> route; documents/video get /original/as/<seoName>.
478
+ - When unauthenticated, you must specify --format (for images) or --original (for documents).
479
+ - Use --mime-type <type> to skip the metadata lookup when you already know the type
480
+ (e.g. from a prior search result's dc:format field).
481
+ - search --first-url auto-detects using the dc:format from the search hit (no extra call).
482
+
401
483
  Important defaults:
402
- - Bucket comes from --bucket or AEMDM_BUCKET.
403
- - A standalone call like aemdm --bucket delivery-p123-e456.adobeaemcloud.com saves the default bucket to the local aemdm profile config.
404
- - aemdm --ims-token <token> saves the IMS token to the profile config. Both can be saved together.
405
- - Search auth comes from --ims-token/AEMDM_IMS_TOKEN/profile config and --api-key/AEMDM_API_KEY.
406
- - asset get prints a URL by default.
484
+ - asset get prints a delivery URL by default.
407
485
  - asset get --metadata prints full JSON metadata when authenticated, or basic public JSON metadata when no token is supplied.
408
486
  - asset get --binary downloads the asset and requires --output.
409
487
  - search --first-id prints one asset ID for piping.
@@ -416,6 +494,7 @@ Asset URL examples:
416
494
  - aemdm asset get urn:aaid:aem:1234 --original --binary --output ./asset.bin
417
495
  - aemdm asset get urn:aaid:aem:1234 --metadata
418
496
  - aemdm asset get urn:aaid:aem:1234 --metadata --ims-token <token>
497
+ - aemdm asset get urn:aaid:aem:1234 --mime-type application/pdf
419
498
 
420
499
  Search examples:
421
500
  - aemdm search --text "hero banner"
@@ -441,8 +520,12 @@ LLM usage guidance:
441
520
  - Use search when you need to discover an asset by metadata or text.
442
521
  - Prefer --first-id or --ids-only when another CLI call needs asset IDs.
443
522
  - Prefer --first-url when the user wants a delivery URL from a search result.
523
+ --first-url uses the dc:format from the search hit to pick the correct route automatically.
444
524
  - Prefer --first-metadata when the user wants the resolved asset metadata after search.
445
525
  - Prefer --first-binary with --output when the user wants the downloaded file.
526
+ - When piping search results to asset get, pass --mime-type with the dc:format from the
527
+ search result to avoid an extra metadata call and ensure the correct route.
528
+ Example: aemdm asset get <id> --mime-type application/pdf
446
529
  `;
447
530
  }
448
531
  function parseStandaloneConfig(argv) {
@@ -5,6 +5,9 @@ export const TRANSFORM_FALLBACK_FORMAT = "png";
5
5
  export const deliveryFormatSchema = z.enum(["gif", "png", "jpg", "jpeg", "webp", "avif"]);
6
6
  const qualitySchema = z.number().int().min(1).max(100);
7
7
  const dimensionSchema = z.number().int().min(1);
8
+ function isImageMimeType(mimeType) {
9
+ return mimeType.startsWith("image/");
10
+ }
8
11
  export function parseSize(value) {
9
12
  const match = /^(?<width>\d+)?x(?<height>\d+)?$/i.exec(value.trim());
10
13
  if (!match?.groups) {
@@ -58,7 +61,9 @@ export function buildAssetUrl(baseUrl, options) {
58
61
  if (options.height !== undefined) {
59
62
  dimensionSchema.parse(options.height);
60
63
  }
61
- if (options.original) {
64
+ const useOriginal = options.original ||
65
+ (options.mimeType !== undefined && !isImageMimeType(options.mimeType));
66
+ if (useOriginal) {
62
67
  return `${normalizedBase}/${encodedAssetId}/original/as/${encodedSeoName}`;
63
68
  }
64
69
  const shouldUseTransformRoute = options.format !== undefined ||
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aemdm",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "CLI for Adobe Dynamic Media with OpenAPI",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Chris Pilsworth",
@@ -41,6 +41,7 @@
41
41
  "node": ">=20"
42
42
  },
43
43
  "dependencies": {
44
+ "aemdm": "^0.4.1",
44
45
  "commander": "^14.0.1",
45
46
  "zod": "^4.1.12"
46
47
  },