@farthershore/cli 0.3.6 → 0.3.8
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 +160 -0
- package/dist/client.d.ts +31 -0
- package/dist/client.js +32 -3
- package/dist/commands/apply.js +74 -20
- package/dist/commands/billing.d.ts +3 -0
- package/dist/commands/billing.js +59 -0
- package/dist/commands/feature.d.ts +3 -0
- package/dist/commands/feature.js +80 -0
- package/dist/commands/helpers.d.ts +15 -0
- package/dist/commands/helpers.js +93 -0
- package/dist/commands/login.js +47 -38
- package/dist/commands/meter.d.ts +3 -0
- package/dist/commands/meter.js +94 -0
- package/dist/commands/plan-transition.d.ts +40 -0
- package/dist/commands/plan-transition.js +504 -0
- package/dist/commands/plan.d.ts +3 -0
- package/dist/commands/plan.js +185 -0
- package/dist/commands/product.d.ts +3 -0
- package/dist/commands/product.js +101 -0
- package/dist/commands/transition.d.ts +3 -0
- package/dist/commands/transition.js +63 -0
- package/dist/index.js +14 -0
- package/dist/types.d.ts +2 -0
- package/package.json +3 -3
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { saveConfig } from "../config.js";
|
|
2
|
+
import * as output from "../output.js";
|
|
3
|
+
import { commandFormat, printResult, resolveProductId } from "./helpers.js";
|
|
4
|
+
export function registerProductCommands(program, getClient) {
|
|
5
|
+
const product = program.command("product").description("Manage products");
|
|
6
|
+
product
|
|
7
|
+
.command("create <name>")
|
|
8
|
+
.description("Create a product and select it for subsequent CLI commands")
|
|
9
|
+
.requiredOption("--base-url <url>", "Backend API base URL")
|
|
10
|
+
.option("--strategy <strategy>", "Product billing strategy", "flat_subscription")
|
|
11
|
+
.option("--description <desc>", "Product description")
|
|
12
|
+
.option("--display-name <name>", "Display name")
|
|
13
|
+
.option("--dry-run", "Print the create request without mutating")
|
|
14
|
+
.action(async (name, opts) => {
|
|
15
|
+
if (opts.dryRun) {
|
|
16
|
+
printResult(program, "Product create dry run", {
|
|
17
|
+
ok: true,
|
|
18
|
+
dryRun: true,
|
|
19
|
+
proposal: {
|
|
20
|
+
name,
|
|
21
|
+
baseUrl: opts.baseUrl,
|
|
22
|
+
description: opts.description,
|
|
23
|
+
displayName: opts.displayName,
|
|
24
|
+
billingStrategy: opts.strategy,
|
|
25
|
+
subscriberChangePolicy: {
|
|
26
|
+
default: "preserve_current_period",
|
|
27
|
+
proration: "none",
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
nextActions: ["Run without --dry-run to create the product"],
|
|
31
|
+
});
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
const client = getClient();
|
|
35
|
+
const result = await client.initProduct({
|
|
36
|
+
name,
|
|
37
|
+
baseUrl: opts.baseUrl,
|
|
38
|
+
description: opts.description,
|
|
39
|
+
displayName: opts.displayName,
|
|
40
|
+
billingStrategy: opts.strategy,
|
|
41
|
+
subscriberChangePolicy: {
|
|
42
|
+
default: "preserve_current_period",
|
|
43
|
+
proration: "none",
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
saveConfig({
|
|
47
|
+
activeProductId: result.product.id,
|
|
48
|
+
activeProductName: result.product.name,
|
|
49
|
+
});
|
|
50
|
+
printResult(program, `Created product "${result.product.name}"`, {
|
|
51
|
+
ok: true,
|
|
52
|
+
productId: result.product.id,
|
|
53
|
+
product: result.product,
|
|
54
|
+
repo: result.repo,
|
|
55
|
+
githubSyncStatus: result.product
|
|
56
|
+
.acceptedProductSpecGithubSyncStatus ?? null,
|
|
57
|
+
nextActions: [
|
|
58
|
+
"Add meters, features, and plans",
|
|
59
|
+
"Run farthershore apply --dry-run --format json",
|
|
60
|
+
],
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
product
|
|
64
|
+
.command("status")
|
|
65
|
+
.description("Show selected product status")
|
|
66
|
+
.option("--product <product>", "Product id or name")
|
|
67
|
+
.action(async (opts) => {
|
|
68
|
+
const client = getClient();
|
|
69
|
+
const productId = await resolveProductId(client, opts.product);
|
|
70
|
+
const config = await client.getProductConfig(productId);
|
|
71
|
+
printResult(program, "Product status loaded", {
|
|
72
|
+
ok: true,
|
|
73
|
+
productId,
|
|
74
|
+
...(typeof config === "object" && config ? config : {}),
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
product
|
|
78
|
+
.command("config")
|
|
79
|
+
.description("Show latest accepted internal product config")
|
|
80
|
+
.option("--product <product>", "Product id or name")
|
|
81
|
+
.option("--format <format>", "Output format: json or yaml")
|
|
82
|
+
.action(async (opts) => {
|
|
83
|
+
const client = getClient();
|
|
84
|
+
const productId = await resolveProductId(client, opts.product);
|
|
85
|
+
const format = opts.format ?? commandFormat(program);
|
|
86
|
+
if (format === "yaml") {
|
|
87
|
+
console.log(await client.getProductConfigYaml(productId));
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
console.log(output.json(await client.getProductConfig(productId)));
|
|
91
|
+
});
|
|
92
|
+
product
|
|
93
|
+
.command("attempts")
|
|
94
|
+
.description("Show rejected product config attempts")
|
|
95
|
+
.option("--product <product>", "Product id or name")
|
|
96
|
+
.action(async (opts) => {
|
|
97
|
+
const client = getClient();
|
|
98
|
+
const productId = await resolveProductId(client, opts.product);
|
|
99
|
+
console.log(output.json(await client.getProductAttempts(productId)));
|
|
100
|
+
});
|
|
101
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import YAML from "yaml";
|
|
4
|
+
import * as output from "../output.js";
|
|
5
|
+
import { loadAcceptedSpec, resolveProductId } from "./helpers.js";
|
|
6
|
+
import { analyzePlanTransition } from "./plan-transition.js";
|
|
7
|
+
export function registerTransitionCommands(program, getClient) {
|
|
8
|
+
const transition = program
|
|
9
|
+
.command("transition")
|
|
10
|
+
.description("Preview subscriber impact for product config changes");
|
|
11
|
+
transition
|
|
12
|
+
.command("preview")
|
|
13
|
+
.description("Compare the latest accepted config to a proposed config and show subscriber transition impact")
|
|
14
|
+
.option("--product <product>", "Product id or name")
|
|
15
|
+
.option("--to <file>", "Proposed product YAML file")
|
|
16
|
+
.option("--format <format>", "Output format: table or json")
|
|
17
|
+
.action(async (opts) => {
|
|
18
|
+
const client = getClient();
|
|
19
|
+
const productId = await resolveProductId(client, opts.product);
|
|
20
|
+
const accepted = await loadAcceptedSpec(client, productId);
|
|
21
|
+
const proposed = loadProposedSpec(opts.to) ?? accepted;
|
|
22
|
+
const analysis = analyzePlanTransition({
|
|
23
|
+
fromSpec: accepted,
|
|
24
|
+
toSpec: proposed,
|
|
25
|
+
fromLabel: "acceptedProductSpec",
|
|
26
|
+
toLabel: opts.to ?? "acceptedProductSpec",
|
|
27
|
+
});
|
|
28
|
+
const globalOpts = program.opts();
|
|
29
|
+
const format = opts.format ?? output.outputFormat(globalOpts.format);
|
|
30
|
+
if (format === "json") {
|
|
31
|
+
console.log(output.json({
|
|
32
|
+
ok: analysis.valid,
|
|
33
|
+
productId,
|
|
34
|
+
transitionPreview: analysis,
|
|
35
|
+
errors: analysis.errors,
|
|
36
|
+
warnings: analysis.warnings,
|
|
37
|
+
nextActions: analysis.agentHints,
|
|
38
|
+
}));
|
|
39
|
+
if (!analysis.valid)
|
|
40
|
+
process.exitCode = 1;
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
if (analysis.valid)
|
|
44
|
+
output.success("Transition preview passed");
|
|
45
|
+
else
|
|
46
|
+
output.error("Transition preview failed");
|
|
47
|
+
for (const change of analysis.changes) {
|
|
48
|
+
console.log(` • ${change.message} -> ${change.subscriberAction}`);
|
|
49
|
+
}
|
|
50
|
+
for (const warning of analysis.warnings)
|
|
51
|
+
output.warn(warning);
|
|
52
|
+
for (const error of analysis.errors)
|
|
53
|
+
output.error(error);
|
|
54
|
+
if (!analysis.valid)
|
|
55
|
+
process.exitCode = 1;
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
function loadProposedSpec(file) {
|
|
59
|
+
const path = resolve(file ?? "product.yaml");
|
|
60
|
+
if (!existsSync(path))
|
|
61
|
+
return null;
|
|
62
|
+
return YAML.parse(readFileSync(path, "utf-8"));
|
|
63
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -10,6 +10,13 @@ import { registerAuthCommands } from "./commands/login.js";
|
|
|
10
10
|
import { registerInitCommand } from "./commands/init.js";
|
|
11
11
|
import { registerValidateCommand } from "./commands/validate.js";
|
|
12
12
|
import { registerApplyCommand } from "./commands/apply.js";
|
|
13
|
+
import { registerBillingCommands } from "./commands/billing.js";
|
|
14
|
+
import { registerFeatureCommands } from "./commands/feature.js";
|
|
15
|
+
import { registerMeterCommands } from "./commands/meter.js";
|
|
16
|
+
import { registerPlanTransitionCommand } from "./commands/plan-transition.js";
|
|
17
|
+
import { registerPlanCommands } from "./commands/plan.js";
|
|
18
|
+
import { registerProductCommands } from "./commands/product.js";
|
|
19
|
+
import { registerTransitionCommands } from "./commands/transition.js";
|
|
13
20
|
import { CliError } from "./types.js";
|
|
14
21
|
import { getRemediation } from "./remediation.js";
|
|
15
22
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
@@ -33,7 +40,14 @@ function getClient() {
|
|
|
33
40
|
// Register commands
|
|
34
41
|
registerAuthCommands(program);
|
|
35
42
|
registerInitCommand(program, getClient);
|
|
43
|
+
registerProductCommands(program, getClient);
|
|
44
|
+
registerBillingCommands(program, getClient);
|
|
45
|
+
registerMeterCommands(program, getClient);
|
|
46
|
+
registerFeatureCommands(program, getClient);
|
|
47
|
+
registerPlanCommands(program, getClient);
|
|
48
|
+
registerTransitionCommands(program, getClient);
|
|
36
49
|
registerValidateCommand(program);
|
|
50
|
+
registerPlanTransitionCommand(program);
|
|
37
51
|
registerApplyCommand(program, getClient);
|
|
38
52
|
// Global error handler
|
|
39
53
|
program.exitOverride();
|
package/dist/types.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@farthershore/cli",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.8",
|
|
4
4
|
"description": "FartherShore CLI — create and configure API products",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
|
-
"farthershore": "
|
|
7
|
+
"farthershore": "dist/index.js"
|
|
8
8
|
},
|
|
9
9
|
"files": [
|
|
10
10
|
"dist"
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
},
|
|
18
18
|
"repository": {
|
|
19
19
|
"type": "git",
|
|
20
|
-
"url": "https://github.com/farther-shore/farthershore.git"
|
|
20
|
+
"url": "git+https://github.com/farther-shore/farthershore.git"
|
|
21
21
|
},
|
|
22
22
|
"scripts": {
|
|
23
23
|
"dev": "tsx src/index.ts",
|