@lukoweb/apitogo 0.2.46 → 0.2.49
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/cli.js +1176 -3106
- package/docs/cli/overview.md +91 -91
- package/docs/cli/shape.md +127 -127
- package/docs/cli/walkthrough.md +14 -25
- package/docs/configuration/make-config.mdx +97 -124
- package/docs/configuration/manifest.md +129 -128
- package/docs/configuration/navigation.mdx +415 -421
- package/docs/configuration/plans.md +154 -153
- package/docs/guides/custom-pages.md +111 -110
- package/package.json +1 -1
- package/src/config/v2/__fixtures__/scenario-a-docs-only.json +6 -6
- package/src/config/v2/__fixtures__/scenario-b-managed-origin.json +14 -14
- package/src/config/v2/__fixtures__/scenario-c-proxy-oidc.json +15 -15
- package/src/config/v2/__fixtures__/scenario-h-full-stack.json +28 -28
- package/src/config/v2/billing.ts +59 -0
- package/src/config/v2/docs-navigation.ts +72 -72
- package/src/config/v2/errors.ts +42 -42
- package/src/config/v2/gateway-reference.ts +220 -220
- package/src/config/v2/gateway-request.ts +123 -123
- package/src/config/v2/index.ts +7 -0
- package/src/config/v2/layout.ts +237 -237
- package/src/config/v2/load.ts +70 -70
- package/src/config/v2/parse.ts +214 -214
- package/src/config/v2/portal-profile.ts +88 -88
- package/src/config/v2/resolve.ts +6 -1
- package/src/config/v2/rules.ts +27 -15
- package/src/config/v2/services.ts +6 -21
package/src/config/v2/rules.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { readdir, readFile, stat } from "node:fs/promises";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { glob } from "glob";
|
|
4
4
|
import { parse as parseYaml } from "yaml";
|
|
5
|
+
import { billingNeeded, stripeKeySecretName } from "./billing.js";
|
|
5
6
|
import type { ConfigIssue } from "./errors.js";
|
|
6
7
|
import { type ResolvedConfigV2, toGlobPath } from "./resolve.js";
|
|
7
8
|
import type { ConfigV2 } from "./schema.js";
|
|
@@ -657,9 +658,9 @@ function identityRules(
|
|
|
657
658
|
// --- plans (2.6) ----------------------------------------------------------------------------------------------------
|
|
658
659
|
|
|
659
660
|
async function plansRules(
|
|
660
|
-
config: ConfigV2,
|
|
661
661
|
resolved: ResolvedConfigV2,
|
|
662
662
|
out: Collector,
|
|
663
|
+
paid: boolean,
|
|
663
664
|
) {
|
|
664
665
|
const plans = resolved.plans;
|
|
665
666
|
if (!plans?.enabled) return;
|
|
@@ -683,16 +684,8 @@ async function plansRules(
|
|
|
683
684
|
}
|
|
684
685
|
}
|
|
685
686
|
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
out.error({
|
|
689
|
-
code: "PLANS_BILLING_INCOMPLETE",
|
|
690
|
-
path: "plans.billing.apiKey",
|
|
691
|
-
message:
|
|
692
|
-
"plans.billing.apiKey is required when billing.provider is stripe.",
|
|
693
|
-
hint: 'Set it to a secret reference, for example "secret:STRIPE_API_KEY".',
|
|
694
|
-
});
|
|
695
|
-
}
|
|
687
|
+
// Billing is on only when a plan has a price. The Stripe key defaults to the STRIPE_API_KEY secret.
|
|
688
|
+
if (paid) {
|
|
696
689
|
const headless = !(resolved.portal?.enabled === true);
|
|
697
690
|
for (const field of ["successUrl", "cancelUrl"] as const) {
|
|
698
691
|
if (headless && !plans.billing[field]) {
|
|
@@ -711,11 +704,22 @@ async function plansRules(
|
|
|
711
704
|
|
|
712
705
|
function secretRules(
|
|
713
706
|
config: ConfigV2,
|
|
707
|
+
resolved: ResolvedConfigV2,
|
|
714
708
|
context: CheckContext,
|
|
715
709
|
out: Collector,
|
|
716
710
|
skipped: string[],
|
|
711
|
+
paid: boolean,
|
|
717
712
|
) {
|
|
718
|
-
|
|
713
|
+
// The billing secrets matter only when a plan has a price. The Stripe key is then needed even when the config does not
|
|
714
|
+
// name it (it defaults to STRIPE_API_KEY). `apitogo deploy` asks for it, so a missing key is a warning, not an error.
|
|
715
|
+
const stripeKeyPath = "plans.billing.apiKey";
|
|
716
|
+
const named = collectSecretRefs(config).filter(
|
|
717
|
+
(ref) => paid || !ref.path.startsWith("plans.billing"),
|
|
718
|
+
);
|
|
719
|
+
const refs =
|
|
720
|
+
paid && !named.some((ref) => ref.path === stripeKeyPath)
|
|
721
|
+
? [...named, { name: stripeKeySecretName(resolved), path: stripeKeyPath }]
|
|
722
|
+
: named;
|
|
719
723
|
if (refs.length === 0) return;
|
|
720
724
|
if (!context.knownSecrets) {
|
|
721
725
|
skipped.push(
|
|
@@ -724,7 +728,14 @@ function secretRules(
|
|
|
724
728
|
return;
|
|
725
729
|
}
|
|
726
730
|
for (const ref of refs) {
|
|
727
|
-
if (!context.knownSecrets.has(ref.name)) {
|
|
731
|
+
if (ref.path === stripeKeyPath && !context.knownSecrets.has(ref.name)) {
|
|
732
|
+
out.warn({
|
|
733
|
+
code: "SECRET_NOT_SET",
|
|
734
|
+
path: ref.path,
|
|
735
|
+
message: `a plan has a price, and the Stripe key (secret ${ref.name}) is not set yet.`,
|
|
736
|
+
hint: `apitogo deploy asks for it. Or run: apitogo secrets set ${ref.name}`,
|
|
737
|
+
});
|
|
738
|
+
} else if (!context.knownSecrets.has(ref.name)) {
|
|
728
739
|
out.error({
|
|
729
740
|
code: "SECRET_NOT_SET",
|
|
730
741
|
path: ref.path,
|
|
@@ -751,12 +762,13 @@ export async function checkConfigV2(
|
|
|
751
762
|
warn: (issue) => warnings.push(issue),
|
|
752
763
|
};
|
|
753
764
|
|
|
765
|
+
const paid = await billingNeeded(resolved);
|
|
754
766
|
dependencyRules(config, resolved, out);
|
|
755
|
-
secretRules(config, context, out, skipped);
|
|
767
|
+
secretRules(config, resolved, context, out, skipped, paid);
|
|
756
768
|
await portalRules(config, resolved, out);
|
|
757
769
|
await gatewayRules(resolved, out);
|
|
758
770
|
identityRules(config, resolved, out);
|
|
759
|
-
await plansRules(
|
|
771
|
+
await plansRules(resolved, out, paid);
|
|
760
772
|
|
|
761
773
|
return { errors, warnings, skipped };
|
|
762
774
|
}
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
} from "node:fs/promises";
|
|
10
10
|
import path from "node:path";
|
|
11
11
|
import { ApitogoError } from "../../lib/util/invariant.js";
|
|
12
|
+
import { isPaidPlan } from "./billing.js";
|
|
12
13
|
import {
|
|
13
14
|
FULL_SERVER_HEALTH_PATH,
|
|
14
15
|
FULL_SERVER_TIMEOUT_SECONDS,
|
|
@@ -182,7 +183,6 @@ export type AddOptions = {
|
|
|
182
183
|
plans?: {
|
|
183
184
|
file?: string;
|
|
184
185
|
template?: "free" | "tiered" | "none";
|
|
185
|
-
billing?: "stripe" | "none";
|
|
186
186
|
};
|
|
187
187
|
};
|
|
188
188
|
|
|
@@ -773,15 +773,8 @@ async function buildPlans(
|
|
|
773
773
|
: isFilled(options.template)
|
|
774
774
|
? "starter"
|
|
775
775
|
: "free");
|
|
776
|
-
//
|
|
777
|
-
//
|
|
778
|
-
const billing = o.billing ?? (template === "tiered" ? "stripe" : "none");
|
|
779
|
-
if (template === "tiered" && billing === "none") {
|
|
780
|
-
throw new ServiceEditError(
|
|
781
|
-
"The tiered template has paid plans, which need a billing provider.",
|
|
782
|
-
"Use --billing stripe, or --template free.",
|
|
783
|
-
);
|
|
784
|
-
}
|
|
776
|
+
// Stripe is the only billing provider, and it is needed only once a plan has a price: the config names no billing
|
|
777
|
+
// settings, and `apitogo deploy` asks for the Stripe key (secret STRIPE_API_KEY) the first time a paid plan is deployed.
|
|
785
778
|
const file = o.file ? insideProject(dir, o.file, "Plans file") : undefined;
|
|
786
779
|
const target = file
|
|
787
780
|
? path.join(dir, file)
|
|
@@ -791,22 +784,14 @@ async function buildPlans(
|
|
|
791
784
|
target,
|
|
792
785
|
`${JSON.stringify({ version: 1, plans: PLAN_TEMPLATES[template] }, null, 2)}\n`,
|
|
793
786
|
);
|
|
794
|
-
if (
|
|
787
|
+
if (PLAN_TEMPLATES[template].some((plan) => isPaidPlan(plan))) {
|
|
795
788
|
context.notes.push(
|
|
796
|
-
"
|
|
789
|
+
"A plan has a price, so payments go through Stripe: apitogo deploy asks for your Stripe secret key.",
|
|
797
790
|
);
|
|
798
791
|
}
|
|
799
792
|
return {
|
|
800
793
|
...(options.disabled ? { enabled: false } : {}),
|
|
801
794
|
...(file ? { file } : {}),
|
|
802
|
-
...(billing === "stripe"
|
|
803
|
-
? {
|
|
804
|
-
billing: {
|
|
805
|
-
provider: "stripe" as const,
|
|
806
|
-
apiKey: "secret:STRIPE_API_KEY",
|
|
807
|
-
},
|
|
808
|
-
}
|
|
809
|
-
: { billing: { provider: "none" as const } }),
|
|
810
795
|
};
|
|
811
796
|
}
|
|
812
797
|
|
|
@@ -981,7 +966,7 @@ export async function planRemoveService(
|
|
|
981
966
|
}
|
|
982
967
|
|
|
983
968
|
/** Replaces the config file atomically: a failure leaves the old file in place. */
|
|
984
|
-
async function writeConfig(
|
|
969
|
+
export async function writeConfig(
|
|
985
970
|
configPath: string,
|
|
986
971
|
config: ConfigV2,
|
|
987
972
|
): Promise<void> {
|