fresh-squeezy 0.1.19 → 0.2.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 +6 -1
- package/dist/cli.js +304 -10
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -137,6 +137,11 @@ npx fresh-squeezy create webhook --body-file webhook.json --mode test
|
|
|
137
137
|
npx fresh-squeezy cancel subscription --id 9 --yes
|
|
138
138
|
npx fresh-squeezy refund order --id 100 --yes --mode live
|
|
139
139
|
npx fresh-squeezy generate-invoice order --id 100 --yes
|
|
140
|
+
|
|
141
|
+
# Bulk delete / cancel / refund: --all, --ids, --match, --dry-run
|
|
142
|
+
npx fresh-squeezy delete discount --all --store-ids 12 --dry-run
|
|
143
|
+
npx fresh-squeezy delete discount --all --store-ids 12 --match TEST --yes
|
|
144
|
+
npx fresh-squeezy delete webhook --ids 1,2,3 --yes
|
|
140
145
|
```
|
|
141
146
|
|
|
142
147
|
**Safety:** `delete` / `cancel` / `refund` always require `--yes` or a TTY confirm. Live-mode writes need `--yes` or TTY confirm too. Bodies are JSON:API documents via `--body` or `--body-file` (not flat flags).
|
|
@@ -206,7 +211,7 @@ The CLI default is `--mode test`. Override with `--mode live`. Guided setup asks
|
|
|
206
211
|
| `LEMON_SQUEEZY_MODE` | no | `test` (default) or `live` |
|
|
207
212
|
| `LEMON_SQUEEZY_STORE_ID` | no | Convenience default for `client.doctor()` — library only |
|
|
208
213
|
|
|
209
|
-
|
|
214
|
+
`doctor` and `validate` do not read `LEMON_SQUEEZY_STORE_ID`; use `--store-ids` or `--all-stores` there. Only the `ops` verbs (`list`, `delete --all`, …) fall back to it when `--store-ids` is omitted.
|
|
210
215
|
|
|
211
216
|
## Issue codes
|
|
212
217
|
|
package/dist/cli.js
CHANGED
|
@@ -2477,6 +2477,10 @@ var pickLauncherAction = async () => {
|
|
|
2477
2477
|
name: "Run doctor now \u2014 pick stores interactively when needed",
|
|
2478
2478
|
value: "doctor"
|
|
2479
2479
|
},
|
|
2480
|
+
{
|
|
2481
|
+
name: "Manage resources \u2014 bulk delete / cancel / refund",
|
|
2482
|
+
value: "manage"
|
|
2483
|
+
},
|
|
2480
2484
|
{
|
|
2481
2485
|
name: "Show command examples \u2014 copy/paste friendly",
|
|
2482
2486
|
value: "examples"
|
|
@@ -2712,6 +2716,29 @@ var isPromptCancel = (error) => {
|
|
|
2712
2716
|
if (!(error instanceof Error)) return false;
|
|
2713
2717
|
return error.name === "ExitPromptError" || error.message.includes("User force closed");
|
|
2714
2718
|
};
|
|
2719
|
+
var pickManageOperation = async (ops) => {
|
|
2720
|
+
return select({
|
|
2721
|
+
message: "Which operation?",
|
|
2722
|
+
theme: PROMPT_THEME,
|
|
2723
|
+
choices: ops.map((op) => ({ name: `${op.verb} ${op.resource}`, value: op }))
|
|
2724
|
+
});
|
|
2725
|
+
};
|
|
2726
|
+
var askParentId = async (resource) => {
|
|
2727
|
+
const value = await input({
|
|
2728
|
+
message: `Parent id to list ${resource} items from:`,
|
|
2729
|
+
theme: PROMPT_THEME,
|
|
2730
|
+
validate: (entry) => entry.trim().length > 0 ? true : "A parent id is required."
|
|
2731
|
+
});
|
|
2732
|
+
return value.trim();
|
|
2733
|
+
};
|
|
2734
|
+
var pickBulkTargets = async (verb, items) => {
|
|
2735
|
+
return checkbox({
|
|
2736
|
+
message: `Pick items to ${verb} (space toggles, a selects all, enter confirms):`,
|
|
2737
|
+
theme: PROMPT_THEME,
|
|
2738
|
+
choices: items.map((item) => ({ name: `${item.label} \u2014 id ${item.id}`, value: item.id })),
|
|
2739
|
+
validate: (selected) => selected.length > 0 ? true : "Pick at least one item."
|
|
2740
|
+
});
|
|
2741
|
+
};
|
|
2715
2742
|
|
|
2716
2743
|
// src/cli/resolveStores.ts
|
|
2717
2744
|
var resolveStores = async (client, input2) => {
|
|
@@ -2947,6 +2974,11 @@ var renderCommandExamples = () => {
|
|
|
2947
2974
|
" fresh-squeezy validate connection",
|
|
2948
2975
|
" fresh-squeezy validate webhook --store-ids 12 --webhook-url https://app.example.com/api/webhooks/lemon-squeezy",
|
|
2949
2976
|
"",
|
|
2977
|
+
`${chalk2.bold("Bulk ops (delete / cancel / refund)")}`,
|
|
2978
|
+
" fresh-squeezy delete discount --all --store-ids 12 --dry-run",
|
|
2979
|
+
" fresh-squeezy delete discount --all --store-ids 12 --match TEST --yes",
|
|
2980
|
+
" fresh-squeezy delete webhook --ids 1,2,3 --yes",
|
|
2981
|
+
"",
|
|
2950
2982
|
`${chalk2.bold("Automation")}`,
|
|
2951
2983
|
" fresh-squeezy doctor --all-stores --json",
|
|
2952
2984
|
""
|
|
@@ -3290,6 +3322,176 @@ var pathExists = async (filePath) => {
|
|
|
3290
3322
|
}
|
|
3291
3323
|
};
|
|
3292
3324
|
|
|
3325
|
+
// src/cli/commands/bulkOps.ts
|
|
3326
|
+
var supportsBulk = (spec) => Boolean(spec.destructive) && spec.idRole === "id";
|
|
3327
|
+
var describeBulkTarget = (item) => {
|
|
3328
|
+
const attrs = item.attributes ?? {};
|
|
3329
|
+
const id = String(item.id);
|
|
3330
|
+
for (const key of ["code", "name", "url", "identifier", "user_email", "status"]) {
|
|
3331
|
+
const value = attrs[key];
|
|
3332
|
+
if (typeof value === "string" && value.length > 0) return { id, label: value };
|
|
3333
|
+
}
|
|
3334
|
+
return { id, label: id };
|
|
3335
|
+
};
|
|
3336
|
+
var matchesText = (item, text) => {
|
|
3337
|
+
const needle = text.toLowerCase();
|
|
3338
|
+
return Object.values(item.attributes ?? {}).some(
|
|
3339
|
+
(value) => typeof value === "string" && value.toLowerCase().includes(needle)
|
|
3340
|
+
);
|
|
3341
|
+
};
|
|
3342
|
+
var collectBulkTargets = async (http, spec, selector) => {
|
|
3343
|
+
if (selector.ids?.length) {
|
|
3344
|
+
return selector.ids.map((id) => ({ id, label: id }));
|
|
3345
|
+
}
|
|
3346
|
+
const listSpec = findResourceVerb(spec.resource, "list");
|
|
3347
|
+
if (!listSpec) {
|
|
3348
|
+
throw new FreshSqueezyError({
|
|
3349
|
+
code: "UNKNOWN_OP",
|
|
3350
|
+
message: `Cannot use --all: ${spec.resource} has no list op. Pass --ids instead.`
|
|
3351
|
+
});
|
|
3352
|
+
}
|
|
3353
|
+
if (listSpec.idRole === "store" && !selector.storeId) {
|
|
3354
|
+
throw new FreshSqueezyError({
|
|
3355
|
+
code: "MISSING_ARG",
|
|
3356
|
+
message: "--all needs a store: pass --store-ids or set LEMON_SQUEEZY_STORE_ID."
|
|
3357
|
+
});
|
|
3358
|
+
}
|
|
3359
|
+
if (listSpec.idRole === "parent" && !selector.parentId) {
|
|
3360
|
+
throw new FreshSqueezyError({
|
|
3361
|
+
code: "MISSING_ARG",
|
|
3362
|
+
message: `--all needs --parent-id to list ${spec.resource} items.`
|
|
3363
|
+
});
|
|
3364
|
+
}
|
|
3365
|
+
const items = await invokeOp(http, spec.resource, "list", {
|
|
3366
|
+
storeId: selector.storeId,
|
|
3367
|
+
parentId: selector.parentId
|
|
3368
|
+
});
|
|
3369
|
+
return items.filter((item) => selector.match ? matchesText(item, selector.match) : true).map(describeBulkTarget);
|
|
3370
|
+
};
|
|
3371
|
+
var executeBulk = async (http, spec, targets, opts = {}) => {
|
|
3372
|
+
const dryRun = Boolean(opts.dryRun);
|
|
3373
|
+
const results = [];
|
|
3374
|
+
for (const target of targets) {
|
|
3375
|
+
if (dryRun) {
|
|
3376
|
+
results.push({ ...target, ok: true });
|
|
3377
|
+
continue;
|
|
3378
|
+
}
|
|
3379
|
+
try {
|
|
3380
|
+
await invokeOp(http, spec.resource, spec.verb, { id: target.id, body: opts.body });
|
|
3381
|
+
results.push({ ...target, ok: true });
|
|
3382
|
+
} catch (err) {
|
|
3383
|
+
results.push({
|
|
3384
|
+
...target,
|
|
3385
|
+
ok: false,
|
|
3386
|
+
error: err instanceof Error ? err.message : String(err)
|
|
3387
|
+
});
|
|
3388
|
+
}
|
|
3389
|
+
}
|
|
3390
|
+
const failed = results.filter((r) => !r.ok).length;
|
|
3391
|
+
return { dryRun, total: results.length, succeeded: results.length - failed, failed, results };
|
|
3392
|
+
};
|
|
3393
|
+
var previewTargets = (targets, max = 5) => {
|
|
3394
|
+
const shown = targets.slice(0, max).map((t) => t.label);
|
|
3395
|
+
const more = targets.length - shown.length;
|
|
3396
|
+
return more > 0 ? `${shown.join(", ")}, \u2026 +${more} more` : shown.join(", ");
|
|
3397
|
+
};
|
|
3398
|
+
var renderBulkSummary = (spec, summary) => {
|
|
3399
|
+
if (summary.total === 0) return `No ${spec.resource} items matched \u2014 nothing to ${spec.verb}.
|
|
3400
|
+
`;
|
|
3401
|
+
const verb = summary.dryRun ? `would ${spec.verb}` : spec.verb;
|
|
3402
|
+
const lines = summary.results.map(
|
|
3403
|
+
(r) => r.ok ? ` \u2713 ${verb} ${spec.resource} ${r.id} (${r.label})` : ` \u2717 ${spec.verb} ${spec.resource} ${r.id} (${r.label}): ${r.error}`
|
|
3404
|
+
);
|
|
3405
|
+
const tail = summary.dryRun ? `dry run \u2014 ${summary.total} ${spec.resource} item(s) would be affected, nothing sent` : `${summary.succeeded}/${summary.total} succeeded${summary.failed ? `, ${summary.failed} failed` : ""}`;
|
|
3406
|
+
return `${lines.join("\n")}
|
|
3407
|
+
${tail}
|
|
3408
|
+
`;
|
|
3409
|
+
};
|
|
3410
|
+
|
|
3411
|
+
// src/cli/commands/manage.ts
|
|
3412
|
+
var listManageOperations = () => resourceRegistry.filter((spec) => supportsBulk(spec) && findResourceVerb(spec.resource, "list")).map((spec) => ({ resource: spec.resource, verb: spec.verb }));
|
|
3413
|
+
var renderEquivalentCommand = (input2) => {
|
|
3414
|
+
const parts = ["fresh-squeezy", input2.verb, input2.resource];
|
|
3415
|
+
if (input2.picked.length === input2.total) {
|
|
3416
|
+
parts.push("--all");
|
|
3417
|
+
if (input2.storeId) parts.push("--store-ids", input2.storeId);
|
|
3418
|
+
if (input2.parentId) parts.push("--parent-id", input2.parentId);
|
|
3419
|
+
} else {
|
|
3420
|
+
parts.push("--ids", input2.picked.join(","));
|
|
3421
|
+
}
|
|
3422
|
+
parts.push("--yes");
|
|
3423
|
+
if (input2.mode === "live") parts.push("--mode", "live");
|
|
3424
|
+
return parts.join(" ");
|
|
3425
|
+
};
|
|
3426
|
+
var runManageCommand = async () => {
|
|
3427
|
+
try {
|
|
3428
|
+
const client = createFreshSqueezy();
|
|
3429
|
+
const http = new HttpClient(resolveConfig());
|
|
3430
|
+
const op = await pickManageOperation(listManageOperations());
|
|
3431
|
+
const spec = findResourceVerb(op.resource, op.verb);
|
|
3432
|
+
const listSpec = findResourceVerb(op.resource, "list");
|
|
3433
|
+
if (!spec || !listSpec) {
|
|
3434
|
+
throw new FreshSqueezyError({
|
|
3435
|
+
code: "UNKNOWN_OP",
|
|
3436
|
+
message: `${op.verb} ${op.resource} is not available.`
|
|
3437
|
+
});
|
|
3438
|
+
}
|
|
3439
|
+
process.stdout.write(`
|
|
3440
|
+
${op.verb} ${op.resource} \u2014 mode=${client.mode}
|
|
3441
|
+
`);
|
|
3442
|
+
let scopes = [{}];
|
|
3443
|
+
if (listSpec.idRole === "store") {
|
|
3444
|
+
const { storeIds } = await resolveStores(client, { isInteractive: true });
|
|
3445
|
+
scopes = storeIds.map((storeId) => ({ storeId }));
|
|
3446
|
+
} else if (listSpec.idRole === "parent") {
|
|
3447
|
+
scopes = [{ parentId: await askParentId(op.resource) }];
|
|
3448
|
+
}
|
|
3449
|
+
let exitCode = 0;
|
|
3450
|
+
for (const scope of scopes) {
|
|
3451
|
+
const targets = await collectBulkTargets(http, spec, { all: true, ...scope });
|
|
3452
|
+
if (targets.length === 0) {
|
|
3453
|
+
process.stdout.write(
|
|
3454
|
+
`No ${op.resource} items found${scope.storeId ? ` in store ${scope.storeId}` : ""}.
|
|
3455
|
+
`
|
|
3456
|
+
);
|
|
3457
|
+
continue;
|
|
3458
|
+
}
|
|
3459
|
+
const pickedIds = await pickBulkTargets(op.verb, targets);
|
|
3460
|
+
const picked = targets.filter((t) => pickedIds.includes(t.id));
|
|
3461
|
+
const mark = client.mode === "live" ? "LIVE mode: " : "";
|
|
3462
|
+
const go = await confirmResourceOp(
|
|
3463
|
+
`${mark}${op.verb} ${picked.length} ${op.resource} item(s) (${previewTargets(picked)})?`
|
|
3464
|
+
);
|
|
3465
|
+
if (!go) {
|
|
3466
|
+
process.stdout.write("Skipped \u2014 nothing changed.\n");
|
|
3467
|
+
continue;
|
|
3468
|
+
}
|
|
3469
|
+
const summary = await executeBulk(http, spec, picked);
|
|
3470
|
+
process.stdout.write(renderBulkSummary(spec, summary));
|
|
3471
|
+
process.stdout.write(
|
|
3472
|
+
`
|
|
3473
|
+
Next time:
|
|
3474
|
+
${renderEquivalentCommand({
|
|
3475
|
+
verb: op.verb,
|
|
3476
|
+
resource: op.resource,
|
|
3477
|
+
mode: client.mode,
|
|
3478
|
+
...scope,
|
|
3479
|
+
picked: pickedIds,
|
|
3480
|
+
total: targets.length
|
|
3481
|
+
})}
|
|
3482
|
+
`
|
|
3483
|
+
);
|
|
3484
|
+
if (summary.failed > 0) exitCode = 1;
|
|
3485
|
+
}
|
|
3486
|
+
return exitCode;
|
|
3487
|
+
} catch (err) {
|
|
3488
|
+
if (isPromptCancel(err)) throw err;
|
|
3489
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
3490
|
+
process.stderr.write(renderCliError(message, ["fresh-squeezy ops --list"]));
|
|
3491
|
+
return 2;
|
|
3492
|
+
}
|
|
3493
|
+
};
|
|
3494
|
+
|
|
3293
3495
|
// src/cli/commands/launcher.ts
|
|
3294
3496
|
var runLauncherCommand = async (options) => {
|
|
3295
3497
|
if (!options.isInteractive) return 2;
|
|
@@ -3329,6 +3531,8 @@ var routeLauncherAction = async (action) => {
|
|
|
3329
3531
|
return startGuidedSetup();
|
|
3330
3532
|
case "doctor":
|
|
3331
3533
|
return runDoctorCommand({ isInteractive: true });
|
|
3534
|
+
case "manage":
|
|
3535
|
+
return runManageCommand();
|
|
3332
3536
|
case "examples":
|
|
3333
3537
|
process.stdout.write(renderCommandExamples());
|
|
3334
3538
|
return 0;
|
|
@@ -3380,6 +3584,14 @@ var runResourceOpCommand = async (options) => {
|
|
|
3380
3584
|
);
|
|
3381
3585
|
return 2;
|
|
3382
3586
|
}
|
|
3587
|
+
const bulkError = validateBulkFlags(spec, options);
|
|
3588
|
+
if (bulkError) {
|
|
3589
|
+
writeFatal2(
|
|
3590
|
+
new FreshSqueezyError({ code: "INVALID_ARGS", message: bulkError }),
|
|
3591
|
+
options.json ?? false
|
|
3592
|
+
);
|
|
3593
|
+
return 2;
|
|
3594
|
+
}
|
|
3383
3595
|
try {
|
|
3384
3596
|
const body = readBody(options);
|
|
3385
3597
|
if (spec.body === "required" && body === void 0) {
|
|
@@ -3389,13 +3601,17 @@ var runResourceOpCommand = async (options) => {
|
|
|
3389
3601
|
});
|
|
3390
3602
|
}
|
|
3391
3603
|
const client = createFreshSqueezy({ mode: options.mode });
|
|
3392
|
-
const allowed = await assertWriteSafety(spec, client.mode, options);
|
|
3393
|
-
if (!allowed) return 2;
|
|
3394
3604
|
const config = resolveConfig({ mode: options.mode });
|
|
3395
3605
|
const http = new HttpClient(config);
|
|
3606
|
+
const storeId = options.storeIds?.[0] ?? config.storeId;
|
|
3607
|
+
if (options.all || options.ids) {
|
|
3608
|
+
return await runBulk(http, spec, client.mode, options, { storeId, body });
|
|
3609
|
+
}
|
|
3610
|
+
const allowed = await assertWriteSafety(spec, client.mode, options);
|
|
3611
|
+
if (!allowed) return 2;
|
|
3396
3612
|
const opBody = await invokeOp(http, options.resource, options.verb, {
|
|
3397
3613
|
id: options.id,
|
|
3398
|
-
storeId
|
|
3614
|
+
storeId,
|
|
3399
3615
|
parentId: options.parentId,
|
|
3400
3616
|
body
|
|
3401
3617
|
});
|
|
@@ -3453,22 +3669,74 @@ var writeCatalog = (asJson) => {
|
|
|
3453
3669
|
"\nCatalog resources (product, variant, price, file, store, \u2026) are read-only in the API.\n"
|
|
3454
3670
|
);
|
|
3455
3671
|
};
|
|
3456
|
-
var
|
|
3672
|
+
var validateBulkFlags = (spec, options) => {
|
|
3673
|
+
const bulk = Boolean(options.all || options.ids);
|
|
3674
|
+
if (!bulk) {
|
|
3675
|
+
if (options.match) return "--match needs --all.";
|
|
3676
|
+
if (options.dryRun) return "--dry-run needs --all or --ids.";
|
|
3677
|
+
return void 0;
|
|
3678
|
+
}
|
|
3679
|
+
if (!supportsBulk(spec)) {
|
|
3680
|
+
return `--all/--ids only apply to delete, cancel, and refund ops (not ${spec.verb} ${spec.resource}).`;
|
|
3681
|
+
}
|
|
3682
|
+
if (options.all && options.ids) return "Use either --all or --ids, not both.";
|
|
3683
|
+
if (options.id) return "--id targets one resource; drop it when using --all or --ids.";
|
|
3684
|
+
if (options.match && !options.all) return "--match needs --all.";
|
|
3685
|
+
return void 0;
|
|
3686
|
+
};
|
|
3687
|
+
var runBulk = async (http, spec, mode, options, scope) => {
|
|
3688
|
+
const selector = {
|
|
3689
|
+
all: options.all,
|
|
3690
|
+
ids: options.ids,
|
|
3691
|
+
match: options.match,
|
|
3692
|
+
storeId: scope.storeId,
|
|
3693
|
+
parentId: options.parentId
|
|
3694
|
+
};
|
|
3695
|
+
const targets = await collectBulkTargets(http, spec, selector);
|
|
3696
|
+
if (!options.dryRun && targets.length > 0) {
|
|
3697
|
+
const allowed = await assertWriteSafety(spec, mode, options, {
|
|
3698
|
+
count: targets.length,
|
|
3699
|
+
preview: previewTargets(targets)
|
|
3700
|
+
});
|
|
3701
|
+
if (!allowed) return 2;
|
|
3702
|
+
}
|
|
3703
|
+
const summary = await executeBulk(http, spec, targets, {
|
|
3704
|
+
dryRun: options.dryRun,
|
|
3705
|
+
body: scope.body
|
|
3706
|
+
});
|
|
3707
|
+
if (options.json) {
|
|
3708
|
+
const envelope = {
|
|
3709
|
+
ok: summary.failed === 0,
|
|
3710
|
+
mode,
|
|
3711
|
+
resource: spec.resource,
|
|
3712
|
+
verb: spec.verb,
|
|
3713
|
+
docs: `https://docs.lemonsqueezy.com/api/${spec.docsPath}`,
|
|
3714
|
+
...summary
|
|
3715
|
+
};
|
|
3716
|
+
process.stdout.write(`${JSON.stringify(envelope, null, 2)}
|
|
3717
|
+
`);
|
|
3718
|
+
} else {
|
|
3719
|
+
process.stdout.write(`${spec.verb} ${spec.resource} (mode=${mode})
|
|
3720
|
+
`);
|
|
3721
|
+
process.stdout.write(renderBulkSummary(spec, summary));
|
|
3722
|
+
}
|
|
3723
|
+
return summary.failed > 0 ? 1 : 0;
|
|
3724
|
+
};
|
|
3725
|
+
var assertWriteSafety = async (spec, mode, options, bulk) => {
|
|
3457
3726
|
const isWrite = spec.verb !== "get" && spec.verb !== "list" && spec.verb !== "current-usage";
|
|
3458
3727
|
if (!isWrite) return true;
|
|
3459
3728
|
const needsGate = Boolean(spec.destructive) || mode === "live";
|
|
3460
3729
|
if (!needsGate) return true;
|
|
3461
3730
|
if (options.yes) return true;
|
|
3462
3731
|
if (options.isInteractive) {
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
);
|
|
3732
|
+
const what = bulk ? `${spec.verb} ${bulk.count} ${spec.resource} item(s) (${bulk.preview})` : `${spec.verb} ${spec.resource}`;
|
|
3733
|
+
return confirmResourceOp(mode === "live" ? `LIVE mode: allow ${what}?` : `Confirm ${what}?`);
|
|
3466
3734
|
}
|
|
3467
3735
|
process.stderr.write(
|
|
3468
3736
|
renderCliError(
|
|
3469
3737
|
`${spec.verb} ${spec.resource} requires --yes in non-interactive ${mode} mode${spec.destructive ? " (destructive op)" : ""}`,
|
|
3470
3738
|
[
|
|
3471
|
-
`fresh-squeezy ${spec.verb} ${spec.resource} --yes \u2026`,
|
|
3739
|
+
`fresh-squeezy ${spec.verb} ${spec.resource}${bulk ? " --all" : ""} --yes \u2026`,
|
|
3472
3740
|
"Use test mode for safer experimentation: --mode test"
|
|
3473
3741
|
]
|
|
3474
3742
|
)
|
|
@@ -3840,6 +4108,28 @@ program.command("ops").description("List docs-backed resource ops (Lemon Squeezy
|
|
|
3840
4108
|
})
|
|
3841
4109
|
);
|
|
3842
4110
|
});
|
|
4111
|
+
var BULK_VERBS = ["delete", "cancel", "refund"];
|
|
4112
|
+
var opHelpText = (verb) => {
|
|
4113
|
+
if (!BULK_VERBS.includes(verb)) {
|
|
4114
|
+
return `
|
|
4115
|
+
Example:
|
|
4116
|
+
fresh-squeezy ${verb} <resource> --id 42 --json
|
|
4117
|
+
`;
|
|
4118
|
+
}
|
|
4119
|
+
const r = verb === "delete" ? "discount" : verb === "cancel" ? "subscription" : "order";
|
|
4120
|
+
return `
|
|
4121
|
+
Examples:
|
|
4122
|
+
fresh-squeezy ${verb} ${r} --id 42 --yes
|
|
4123
|
+
fresh-squeezy ${verb} ${r} --ids 1,2,3 --yes
|
|
4124
|
+
fresh-squeezy ${verb} ${r} --all --store-ids 12 --dry-run
|
|
4125
|
+
fresh-squeezy ${verb} ${r} --all --store-ids 12 --match TEST --yes --json
|
|
4126
|
+
|
|
4127
|
+
Bulk notes:
|
|
4128
|
+
--all lists every page first, then asks once (TTY) or needs --yes (non-TTY).
|
|
4129
|
+
Store falls back to LEMON_SQUEEZY_STORE_ID when --store-ids is omitted.
|
|
4130
|
+
Exit codes: 0 ok, 1 some items failed, 2 usage error / declined.
|
|
4131
|
+
`;
|
|
4132
|
+
};
|
|
3843
4133
|
for (const verb of OP_VERBS) {
|
|
3844
4134
|
program.command(`${verb} <resource>`).description(`Docs-backed ${verb} for a Lemon Squeezy resource`).option("-m, --mode <mode>", "test or live", parseMode).option("--id <id>", "Resource id").option(
|
|
3845
4135
|
"--store-ids <ids>",
|
|
@@ -3848,7 +4138,7 @@ for (const verb of OP_VERBS) {
|
|
|
3848
4138
|
).option(
|
|
3849
4139
|
"--parent-id <id>",
|
|
3850
4140
|
"Parent resource id for nested lists (product, order, subscription, \u2026)"
|
|
3851
|
-
).option("--body <json>", "JSON:API body string").option("--body-file <path>", "Path to JSON:API body file").option("--yes", "Skip confirm; required for live writes and destructive ops when non-TTY").option("--json", "Emit machine-readable JSON").action(async (resource, opts) => {
|
|
4141
|
+
).option("--body <json>", "JSON:API body string").option("--body-file <path>", "Path to JSON:API body file").option("--yes", "Skip confirm; required for live writes and destructive ops when non-TTY").option("--json", "Emit machine-readable JSON").option("--all", "Bulk: apply to every item from `list` (delete, cancel, refund)").option("--ids <ids>", "Bulk: comma-separated ids (delete, cancel, refund)", parseCsv).option("--match <text>", "Bulk: with --all, only items whose name/code/url contain <text>").option("--dry-run", "Bulk: show what would be affected; send nothing").addHelpText("after", opHelpText(verb)).action(async (resource, opts) => {
|
|
3852
4142
|
await exitWith(
|
|
3853
4143
|
runResourceOpCommand({
|
|
3854
4144
|
verb,
|
|
@@ -3861,7 +4151,11 @@ for (const verb of OP_VERBS) {
|
|
|
3861
4151
|
bodyFile: opts.bodyFile,
|
|
3862
4152
|
yes: Boolean(opts.yes),
|
|
3863
4153
|
json: Boolean(opts.json),
|
|
3864
|
-
isInteractive
|
|
4154
|
+
isInteractive,
|
|
4155
|
+
all: Boolean(opts.all),
|
|
4156
|
+
ids: opts.ids,
|
|
4157
|
+
match: opts.match,
|
|
4158
|
+
dryRun: Boolean(opts.dryRun)
|
|
3865
4159
|
})
|
|
3866
4160
|
);
|
|
3867
4161
|
});
|