aemdm 0.1.3 → 0.3.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/README.md CHANGED
@@ -189,36 +189,19 @@ aemdm search --raw-query @./query.json
189
189
 
190
190
  ## Publishing
191
191
 
192
- Before publishing a new release, verify the package contents and publish from the repo root:
192
+ The repository includes a `/release` skill for Claude Code that automates the full release process:
193
193
 
194
- ```bash
195
- npm test
196
- npm run pack:check
197
- npm publish
198
194
  ```
199
-
200
- `prepack` builds `dist/`, and `prepublishOnly` runs the test suite during `npm publish`.
201
-
202
- ### Automated npm publish from GitHub
203
-
204
- The repository includes a GitHub Actions workflow that publishes to npm when you push a version tag like `v0.1.3` or publish a GitHub release for that tag.
205
-
206
- Required setup:
207
-
208
- ```bash
209
- NPM_TOKEN
195
+ /release patch # 0.2.1 → 0.2.2
196
+ /release minor # 0.2.1 0.3.0
197
+ /release major # 0.2.1 → 1.0.0
210
198
  ```
211
199
 
212
- Add `NPM_TOKEN` as a GitHub repository secret with permission to publish the `aemdm` package on npm.
213
-
214
- Release flow:
200
+ The skill bumps the version across all files, builds, tests, commits, pushes, creates a git tag, and creates a GitHub release.
215
201
 
216
- ```bash
217
- git tag v0.1.3
218
- git push origin v0.1.3
219
- ```
202
+ Publishing to npm is triggered automatically when a GitHub release is created. The workflow uses npm trusted publishing (OIDC) via the `npm` GitHub environment — no token secret is needed.
220
203
 
221
- The workflow verifies that the tag matches the `version` field in `package.json`, runs `lint`, `build`, and `test`, and then publishes the package.
204
+ The workflow verifies that the release tag matches the `version` field in `package.json`, runs `lint`, `build`, and `test`, and then publishes the package with provenance.
222
205
 
223
206
  ## References
224
207
 
package/dist/cli.js CHANGED
@@ -1,12 +1,18 @@
1
1
  #!/usr/bin/env node
2
- import { pathToFileURL } from "node:url";
2
+ import { fileURLToPath } from "node:url";
3
+ import { realpathSync } from "node:fs";
3
4
  import { Command, CommanderError, Option } from "commander";
4
5
  import { z } from "zod";
5
- import { CliError, request, requestJson, resolveBucket, resolveOptionalImsToken, resolveSearchAuth, } from "./lib/client.js";
6
+ import { CliError, normalizeBucket, request, requestJson, resolveBucket, resolveOptionalImsToken, resolveSearchAuth, } from "./lib/client.js";
6
7
  import { readProfileConfig, writeProfileConfig, } from "./lib/config.js";
7
8
  import { buildAssetUrl, buildMetadataUrl, deliveryFormatSchema, resolveDimensions, } from "./lib/delivery.js";
8
9
  import { buildSearchRequest, getAssetIdFromHit, loadRawQuery, } from "./lib/search.js";
9
10
  import { formatSearchResults, writeBinaryOutput, writeJson, writeLine, } from "./lib/output.js";
11
+ function verbose(runtime, message) {
12
+ if (runtime.verbose) {
13
+ writeLine(runtime.stderr, `[verbose] ${message}`);
14
+ }
15
+ }
10
16
  const numberOption = (flagName) => (value) => {
11
17
  const parsed = Number(value);
12
18
  if (!Number.isInteger(parsed)) {
@@ -161,8 +167,19 @@ async function handleAssetGet(assetId, options, runtime) {
161
167
  const parsed = parseWithSchema(assetGetSchema, options);
162
168
  const profile = await readProfileConfig(runtime.env);
163
169
  const baseUrl = resolveBucket(parsed.bucket, runtime.env, profile.bucket);
164
- const imsToken = resolveOptionalImsToken(parsed.imsToken, runtime.env);
170
+ const imsToken = resolveOptionalImsToken(parsed.imsToken, runtime.env, profile.imsToken);
165
171
  const dimensions = resolveDimensions(parsed.size, parsed.width, parsed.height);
172
+ verbose(runtime, `bucket: ${baseUrl}`);
173
+ verbose(runtime, `asset: ${assetId}`);
174
+ if (imsToken)
175
+ verbose(runtime, `auth: IMS token provided`);
176
+ if (dimensions.width || dimensions.height) {
177
+ verbose(runtime, `dimensions: ${dimensions.width ?? "auto"}x${dimensions.height ?? "auto"}`);
178
+ }
179
+ if (parsed.format)
180
+ verbose(runtime, `format: ${parsed.format}`);
181
+ if (parsed.quality)
182
+ verbose(runtime, `quality: ${parsed.quality}`);
166
183
  if (parsed.metadata) {
167
184
  const metadata = imsToken
168
185
  ? await requestJson(buildMetadataUrl(baseUrl, assetId), {
@@ -210,9 +227,21 @@ async function handleSearch(options, runtime) {
210
227
  const parsed = parseWithSchema(searchSchema, options);
211
228
  const profile = await readProfileConfig(runtime.env);
212
229
  const baseUrl = resolveBucket(parsed.bucket, runtime.env, profile.bucket);
213
- const { imsToken, apiKey } = resolveSearchAuth(parsed.imsToken, parsed.apiKey, runtime.env);
230
+ const { imsToken, apiKey } = resolveSearchAuth(parsed.imsToken, parsed.apiKey, runtime.env, profile.imsToken);
214
231
  const searchBody = await buildSearchBody(parsed);
215
232
  const searchUrl = `${baseUrl}/search`;
233
+ verbose(runtime, `bucket: ${baseUrl}`);
234
+ verbose(runtime, `search: POST ${searchUrl}`);
235
+ verbose(runtime, `api-key: ${apiKey}`);
236
+ verbose(runtime, `auth: IMS token provided`);
237
+ if (parsed.text)
238
+ verbose(runtime, `text: "${parsed.text}"`);
239
+ if (parsed.where.length > 0)
240
+ verbose(runtime, `where: ${parsed.where.join(", ")}`);
241
+ if (parsed.limit !== undefined)
242
+ verbose(runtime, `limit: ${parsed.limit}`);
243
+ if (parsed.rawQuery)
244
+ verbose(runtime, `raw-query: ${parsed.rawQuery}`);
216
245
  const response = await requestJson(searchUrl, {
217
246
  method: "POST",
218
247
  imsToken,
@@ -220,6 +249,9 @@ async function handleSearch(options, runtime) {
220
249
  jsonBody: searchBody,
221
250
  fetchImpl: runtime.fetchImpl,
222
251
  });
252
+ const resultCount = response.hits?.results?.length ?? 0;
253
+ const totalCount = response.search_metadata?.totalCount?.total;
254
+ verbose(runtime, `results: ${resultCount} returned${totalCount !== undefined ? ` (${totalCount} total)` : ""}`);
223
255
  if (parsed.json) {
224
256
  writeJson(runtime.stdout, response);
225
257
  return;
@@ -315,13 +347,17 @@ function buildProgram(runtime) {
315
347
  program
316
348
  .name("aemdm")
317
349
  .description("CLI for Adobe Dynamic Media with OpenAPI")
318
- .version("0.1.0")
350
+ .version("0.3.0")
319
351
  .showHelpAfterError()
352
+ .option("-v, --verbose", "Show additional diagnostic output")
320
353
  .configureOutput({
321
354
  writeOut: (text) => runtime.stdout.write(text),
322
355
  writeErr: (text) => runtime.stderr.write(text),
323
356
  })
324
- .exitOverride();
357
+ .exitOverride()
358
+ .hook("preAction", () => {
359
+ runtime.verbose = program.opts().verbose === true;
360
+ });
325
361
  configureCommonDeliveryOptions(program.command("asset").description("Asset operations"))
326
362
  .command("get <assetId>")
327
363
  .description("Build a delivery URL, fetch metadata, or download a binary for an asset")
@@ -365,7 +401,8 @@ Core commands:
365
401
  Important defaults:
366
402
  - Bucket comes from --bucket or AEMDM_BUCKET.
367
403
  - A standalone call like aemdm --bucket delivery-p123-e456.adobeaemcloud.com saves the default bucket to the local aemdm profile config.
368
- - Search auth comes from --ims-token/AEMDM_IMS_TOKEN and --api-key/AEMDM_API_KEY.
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.
369
406
  - asset get prints a URL by default.
370
407
  - asset get --metadata prints full JSON metadata when authenticated, or basic public JSON metadata when no token is supplied.
371
408
  - asset get --binary downloads the asset and requires --output.
@@ -408,14 +445,31 @@ LLM usage guidance:
408
445
  - Prefer --first-binary with --output when the user wants the downloaded file.
409
446
  `;
410
447
  }
411
- function getStandaloneBucketValue(argv) {
412
- if (argv.length === 1 && argv[0].startsWith("--bucket=")) {
413
- return argv[0].slice("--bucket=".length);
448
+ function parseStandaloneConfig(argv) {
449
+ const config = {};
450
+ const args = [...argv];
451
+ while (args.length > 0) {
452
+ const arg = args.shift();
453
+ if (arg.startsWith("--bucket=")) {
454
+ config.bucket = arg.slice("--bucket=".length);
455
+ }
456
+ else if (arg === "--bucket" && args.length > 0) {
457
+ config.bucket = args.shift();
458
+ }
459
+ else if (arg.startsWith("--ims-token=")) {
460
+ config.imsToken = arg.slice("--ims-token=".length);
461
+ }
462
+ else if (arg === "--ims-token" && args.length > 0) {
463
+ config.imsToken = args.shift();
464
+ }
465
+ else {
466
+ return undefined;
467
+ }
414
468
  }
415
- if (argv.length === 2 && argv[0] === "--bucket") {
416
- return argv[1];
469
+ if (!config.bucket && !config.imsToken) {
470
+ return undefined;
417
471
  }
418
- return undefined;
472
+ return config;
419
473
  }
420
474
  function toCliError(error) {
421
475
  if (error instanceof CliError) {
@@ -442,14 +496,25 @@ export async function runCli(argv, runtimeOverrides = {}) {
442
496
  stdout: process.stdout,
443
497
  stderr: process.stderr,
444
498
  fetchImpl: fetch,
499
+ verbose: false,
445
500
  ...runtimeOverrides,
446
501
  };
447
502
  try {
448
- const standaloneBucket = getStandaloneBucketValue(argv);
449
- if (standaloneBucket !== undefined) {
450
- const bucket = resolveBucket(standaloneBucket, runtime.env);
451
- const configPath = await writeProfileConfig(runtime.env, { bucket });
452
- writeLine(runtime.stdout, `Saved bucket to profile config: ${bucket}`);
503
+ const standaloneConfig = parseStandaloneConfig(argv);
504
+ if (standaloneConfig !== undefined) {
505
+ const existing = await readProfileConfig(runtime.env);
506
+ const merged = { ...existing };
507
+ if (standaloneConfig.bucket) {
508
+ merged.bucket = normalizeBucket(standaloneConfig.bucket);
509
+ }
510
+ if (standaloneConfig.imsToken) {
511
+ merged.imsToken = standaloneConfig.imsToken;
512
+ }
513
+ const configPath = await writeProfileConfig(runtime.env, merged);
514
+ if (merged.bucket)
515
+ writeLine(runtime.stdout, `Saved bucket: ${merged.bucket}`);
516
+ if (standaloneConfig.imsToken)
517
+ writeLine(runtime.stdout, `Saved IMS token to profile config`);
453
518
  writeLine(runtime.stdout, `Config file: ${configPath}`);
454
519
  return 0;
455
520
  }
@@ -470,7 +535,7 @@ export async function runCli(argv, runtimeOverrides = {}) {
470
535
  }
471
536
  }
472
537
  const executedDirectly = process.argv[1] !== undefined &&
473
- import.meta.url === pathToFileURL(process.argv[1]).href;
538
+ fileURLToPath(import.meta.url) === realpathSync(process.argv[1]);
474
539
  if (executedDirectly) {
475
540
  const exitCode = await runCli(process.argv.slice(2));
476
541
  process.exitCode = exitCode;
@@ -40,11 +40,11 @@ export function resolveBucket(explicitValue, env, profileBucket) {
40
40
  }
41
41
  return normalizeBucket(bucket);
42
42
  }
43
- export function resolveOptionalImsToken(explicitValue, env) {
44
- return explicitValue ?? env.AEMDM_IMS_TOKEN;
43
+ export function resolveOptionalImsToken(explicitValue, env, profileImsToken) {
44
+ return explicitValue ?? env.AEMDM_IMS_TOKEN ?? profileImsToken;
45
45
  }
46
- export function resolveSearchAuth(imsToken, apiKey, env) {
47
- const resolvedImsToken = imsToken ?? env.AEMDM_IMS_TOKEN;
46
+ export function resolveSearchAuth(imsToken, apiKey, env, profileImsToken) {
47
+ const resolvedImsToken = imsToken ?? env.AEMDM_IMS_TOKEN ?? profileImsToken;
48
48
  const resolvedApiKey = apiKey ?? env.AEMDM_API_KEY ?? "asset_search_service";
49
49
  if (!resolvedImsToken) {
50
50
  throw new CliError("Missing IMS token. Use --ims-token or set AEMDM_IMS_TOKEN.");
@@ -3,6 +3,7 @@ import path from "node:path";
3
3
  import { z } from "zod";
4
4
  const profileSchema = z.object({
5
5
  bucket: z.string().optional(),
6
+ imsToken: z.string().optional(),
6
7
  });
7
8
  export function resolveConfigPath(env) {
8
9
  if (env.AEMDM_CONFIG_PATH) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aemdm",
3
- "version": "0.1.3",
3
+ "version": "0.3.0",
4
4
  "description": "CLI for Adobe Dynamic Media with OpenAPI",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Chris Pilsworth",