@lukoweb/apitogo 0.1.43 → 0.1.45

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.
@@ -0,0 +1,80 @@
1
+ import { z } from "zod";
2
+ export declare const DEV_PORTAL_MANIFEST_FILENAME = "apitogo.local.json";
3
+ export declare const ManifestPlanInputSchema: z.ZodObject<{
4
+ sku: z.ZodString;
5
+ displayName: z.ZodString;
6
+ isFree: z.ZodBoolean;
7
+ priceAmount: z.ZodOptional<z.ZodNumber>;
8
+ priceCurrency: z.ZodOptional<z.ZodString>;
9
+ billingPeriod: z.ZodOptional<z.ZodEnum<{
10
+ month: "month";
11
+ monthly: "monthly";
12
+ year: "year";
13
+ annual: "annual";
14
+ }>>;
15
+ limitsJson: z.ZodOptional<z.ZodString>;
16
+ includedActionKeys: z.ZodOptional<z.ZodArray<z.ZodString>>;
17
+ }, z.core.$strip>;
18
+ export declare const DevPortalLocalManifestSchema: z.ZodObject<{
19
+ siteName: z.ZodOptional<z.ZodString>;
20
+ branding: z.ZodOptional<z.ZodObject<{
21
+ title: z.ZodOptional<z.ZodString>;
22
+ logoUrl: z.ZodOptional<z.ZodString>;
23
+ }, z.core.$strip>>;
24
+ plans: z.ZodOptional<z.ZodArray<z.ZodObject<{
25
+ sku: z.ZodString;
26
+ displayName: z.ZodString;
27
+ isFree: z.ZodBoolean;
28
+ priceAmount: z.ZodOptional<z.ZodNumber>;
29
+ priceCurrency: z.ZodOptional<z.ZodString>;
30
+ billingPeriod: z.ZodOptional<z.ZodEnum<{
31
+ month: "month";
32
+ monthly: "monthly";
33
+ year: "year";
34
+ annual: "annual";
35
+ }>>;
36
+ limitsJson: z.ZodOptional<z.ZodString>;
37
+ includedActionKeys: z.ZodOptional<z.ZodArray<z.ZodString>>;
38
+ }, z.core.$strip>>>;
39
+ backend: z.ZodOptional<z.ZodObject<{
40
+ sourceDirectory: z.ZodOptional<z.ZodString>;
41
+ publishDirectory: z.ZodOptional<z.ZodString>;
42
+ openApiPath: z.ZodOptional<z.ZodString>;
43
+ runtime: z.ZodOptional<z.ZodString>;
44
+ runtimeVersion: z.ZodOptional<z.ZodString>;
45
+ deployed: z.ZodOptional<z.ZodBoolean>;
46
+ }, z.core.$strip>>;
47
+ gateway: z.ZodOptional<z.ZodObject<{
48
+ authMode: z.ZodOptional<z.ZodEnum<{
49
+ platformManagedEntra: "platformManagedEntra";
50
+ bringYourOwnOidc: "bringYourOwnOidc";
51
+ }>>;
52
+ oidcMetadataUrl: z.ZodOptional<z.ZodString>;
53
+ oidcAllowedIssuers: z.ZodOptional<z.ZodArray<z.ZodString>>;
54
+ oidcAudiences: z.ZodOptional<z.ZodArray<z.ZodString>>;
55
+ limitsJson: z.ZodOptional<z.ZodString>;
56
+ publicHealthActionKey: z.ZodOptional<z.ZodString>;
57
+ }, z.core.$strip>>;
58
+ projectId: z.ZodOptional<z.ZodString>;
59
+ projectName: z.ZodOptional<z.ZodString>;
60
+ }, z.core.$strip>;
61
+ export type ManifestPlanInput = z.infer<typeof ManifestPlanInputSchema>;
62
+ export type DevPortalLocalManifest = z.infer<typeof DevPortalLocalManifestSchema>;
63
+ export type DevPortalPreviewPlan = {
64
+ sku: string;
65
+ displayName: string;
66
+ isFree: boolean;
67
+ priceAmount: number;
68
+ priceCurrency: string;
69
+ billingPeriod: string;
70
+ limitsJson: string | null;
71
+ includedActionKeys: string[];
72
+ };
73
+ export type DevPortalPreviewPlanCatalog = {
74
+ plans: DevPortalPreviewPlan[];
75
+ };
76
+ export declare function normalizeBillingPeriod(period?: string): string;
77
+ export declare function plansToPreviewCatalog(plans: ManifestPlanInput[] | undefined): DevPortalPreviewPlanCatalog;
78
+ export declare function manifestFilePath(rootDir: string): string;
79
+ export declare function parseLocalManifestJson(raw: string): DevPortalLocalManifest | null;
80
+ export declare function readLocalManifest(rootDir: string): Promise<DevPortalLocalManifest | null>;
@@ -45,8 +45,17 @@ VITE_APITOGO_DEV_PORTAL_API_URL=https://your-dev-portal-api.example.com
45
45
  ```
46
46
 
47
47
  **Local preview:** When the backend URL is a placeholder or unreachable, the account area shows a
48
- message that sign-in unlocks after publish. Docs and API reference remain public. Default Free + Pro
49
- plans appear on `/pricing` from `apitogo.local.json` without extra MCP steps.
48
+ message that sign-in unlocks after publish. Docs and API reference remain public. Plans on
49
+ `/pricing` load from `apitogo.local.json` (including `includedActionKeys` per plan). Plan edits
50
+ hot-reload without restarting the dev server.
51
+
52
+ ### Dev portal configuration files
53
+
54
+ | Concern | File |
55
+ | --- | --- |
56
+ | Site nav, docs, plugins, auth wiring | `apitogo.config.tsx` |
57
+ | Plans, rate limits, `includedActionKeys`, project/deploy metadata | `apitogo.local.json` |
58
+ | OIDC and Stripe for publish (MCP only) | `apitogo.local.secrets.json` |
50
59
 
51
60
  **Production:** Register one OIDC app with redirect URI `https://{backend}/signin-oidc` (see MCP
52
61
  `manualSteps.oidcRedirectUri`). The frontend uses cookie sessions via `credentials: "include"` on
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lukoweb/apitogo",
3
- "version": "0.1.43",
3
+ "version": "0.1.45",
4
4
  "type": "module",
5
5
  "sideEffects": [
6
6
  "**/*.css",
@@ -149,6 +149,10 @@
149
149
  "./testing": {
150
150
  "types": "./dist/declarations/lib/testing/index.d.ts",
151
151
  "default": "./src/lib/testing/index.tsx"
152
+ },
153
+ "./local-manifest": {
154
+ "types": "./dist/declarations/config/local-manifest.d.ts",
155
+ "default": "./src/config/local-manifest.ts"
152
156
  }
153
157
  },
154
158
  "scripts": {
@@ -459,6 +463,10 @@
459
463
  "./testing": {
460
464
  "types": "./dist/declarations/lib/testing/index.d.ts",
461
465
  "default": "./src/lib/testing/index.tsx"
466
+ },
467
+ "./local-manifest": {
468
+ "types": "./dist/declarations/config/local-manifest.d.ts",
469
+ "default": "./src/config/local-manifest.ts"
462
470
  }
463
471
  },
464
472
  "access": "public"
@@ -152,10 +152,21 @@ function loadEnv(configEnv: ConfigEnv, rootDir: string) {
152
152
  return { publicEnv, envPrefix };
153
153
  }
154
154
 
155
+ function isWatchableConfigDependency(depPath: string): boolean {
156
+ return !depPath.startsWith("\0virtual:") && !depPath.startsWith("virtual:");
157
+ }
158
+
159
+ function getWatchableConfigDependencies(config: ConfigWithMeta): string[] {
160
+ return [
161
+ config.__meta.configPath,
162
+ ...config.__meta.dependencies.filter(isWatchableConfigDependency),
163
+ ];
164
+ }
165
+
155
166
  async function hasConfigChanged() {
156
167
  if (!config || !modifiedTimes) return true;
157
168
 
158
- const files = [config.__meta.configPath, ...config.__meta.dependencies];
169
+ const files = getWatchableConfigDependencies(config);
159
170
 
160
171
  try {
161
172
  const hasChanged = await Promise.all(
@@ -175,7 +186,7 @@ async function hasConfigChanged() {
175
186
  async function updateModifiedTimes() {
176
187
  if (!config) return;
177
188
 
178
- const files = [config.__meta.configPath, ...config.__meta.dependencies];
189
+ const files = getWatchableConfigDependencies(config);
179
190
 
180
191
  modifiedTimes = new Map();
181
192
 
@@ -0,0 +1,142 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { z } from "zod";
4
+
5
+ export const DEV_PORTAL_MANIFEST_FILENAME = "apitogo.local.json";
6
+
7
+ const BillingPeriodSchema = z.enum([
8
+ "month",
9
+ "year",
10
+ "monthly",
11
+ "annual",
12
+ ]);
13
+
14
+ export const ManifestPlanInputSchema = z.object({
15
+ sku: z.string().min(1),
16
+ displayName: z.string().min(1),
17
+ isFree: z.boolean(),
18
+ priceAmount: z.number().optional(),
19
+ priceCurrency: z.string().optional(),
20
+ billingPeriod: BillingPeriodSchema.optional(),
21
+ limitsJson: z.string().optional(),
22
+ includedActionKeys: z.array(z.string()).optional(),
23
+ });
24
+
25
+ export const DevPortalLocalManifestSchema = z.object({
26
+ siteName: z.string().optional(),
27
+ branding: z
28
+ .object({
29
+ title: z.string().optional(),
30
+ logoUrl: z.string().optional(),
31
+ })
32
+ .optional(),
33
+ plans: z.array(ManifestPlanInputSchema).optional(),
34
+ backend: z
35
+ .object({
36
+ sourceDirectory: z.string().optional(),
37
+ publishDirectory: z.string().optional(),
38
+ openApiPath: z.string().optional(),
39
+ runtime: z.string().optional(),
40
+ runtimeVersion: z.string().optional(),
41
+ deployed: z.boolean().optional(),
42
+ })
43
+ .optional(),
44
+ gateway: z
45
+ .object({
46
+ authMode: z.enum(["platformManagedEntra", "bringYourOwnOidc"]).optional(),
47
+ oidcMetadataUrl: z.string().optional(),
48
+ oidcAllowedIssuers: z.array(z.string()).optional(),
49
+ oidcAudiences: z.array(z.string()).optional(),
50
+ limitsJson: z.string().optional(),
51
+ publicHealthActionKey: z.string().optional(),
52
+ })
53
+ .optional(),
54
+ projectId: z.string().optional(),
55
+ projectName: z.string().optional(),
56
+ });
57
+
58
+ export type ManifestPlanInput = z.infer<typeof ManifestPlanInputSchema>;
59
+ export type DevPortalLocalManifest = z.infer<typeof DevPortalLocalManifestSchema>;
60
+
61
+ export type DevPortalPreviewPlan = {
62
+ sku: string;
63
+ displayName: string;
64
+ isFree: boolean;
65
+ priceAmount: number;
66
+ priceCurrency: string;
67
+ billingPeriod: string;
68
+ limitsJson: string | null;
69
+ includedActionKeys: string[];
70
+ };
71
+
72
+ export type DevPortalPreviewPlanCatalog = {
73
+ plans: DevPortalPreviewPlan[];
74
+ };
75
+
76
+ export function normalizeBillingPeriod(period?: string): string {
77
+ if (!period) {
78
+ return "monthly";
79
+ }
80
+
81
+ const normalized = period.toLowerCase();
82
+ if (normalized === "month") {
83
+ return "monthly";
84
+ }
85
+ if (normalized === "year") {
86
+ return "annual";
87
+ }
88
+ return normalized;
89
+ }
90
+
91
+ export function plansToPreviewCatalog(
92
+ plans: ManifestPlanInput[] | undefined,
93
+ ): DevPortalPreviewPlanCatalog {
94
+ if (!plans?.length) {
95
+ return { plans: [] };
96
+ }
97
+
98
+ return {
99
+ plans: plans.map((plan) => ({
100
+ sku: plan.sku,
101
+ displayName: plan.displayName,
102
+ isFree: plan.isFree,
103
+ priceAmount: plan.priceAmount ?? 0,
104
+ priceCurrency: plan.priceCurrency ?? "usd",
105
+ billingPeriod: normalizeBillingPeriod(plan.billingPeriod),
106
+ limitsJson: plan.limitsJson ?? null,
107
+ includedActionKeys: plan.includedActionKeys ?? [],
108
+ })),
109
+ };
110
+ }
111
+
112
+ export function manifestFilePath(rootDir: string): string {
113
+ return path.join(path.resolve(rootDir), DEV_PORTAL_MANIFEST_FILENAME);
114
+ }
115
+
116
+ export function parseLocalManifestJson(
117
+ raw: string,
118
+ ): DevPortalLocalManifest | null {
119
+ try {
120
+ const parsed = JSON.parse(raw) as unknown;
121
+ const result = DevPortalLocalManifestSchema.safeParse(parsed);
122
+ if (!result.success) {
123
+ return null;
124
+ }
125
+ return result.data;
126
+ } catch {
127
+ return null;
128
+ }
129
+ }
130
+
131
+ export async function readLocalManifest(
132
+ rootDir: string,
133
+ ): Promise<DevPortalLocalManifest | null> {
134
+ const filePath = manifestFilePath(rootDir);
135
+
136
+ try {
137
+ const raw = await readFile(filePath, "utf8");
138
+ return parseLocalManifestJson(raw);
139
+ } catch {
140
+ return null;
141
+ }
142
+ }
package/src/types.d.ts CHANGED
@@ -55,3 +55,8 @@ declare module "virtual:zudoku-shiki-register" {
55
55
  import type { HighlighterCore } from "shiki/core";
56
56
  export const registerShiki: (highlighter: HighlighterCore) => Promise<void>;
57
57
  }
58
+
59
+ declare module "virtual:apitogo-local-manifest" {
60
+ const catalog: import("./config/local-manifest.ts").DevPortalPreviewPlanCatalog;
61
+ export default catalog;
62
+ }
@@ -1,76 +1,10 @@
1
- import { readFile } from "node:fs/promises";
2
1
  import path from "node:path";
3
2
  import { loadEnv } from "vite";
4
3
 
5
- export const DEV_PORTAL_MANIFEST_FILENAME = "apitogo.local.json";
6
-
7
4
  export const DEV_PORTAL_VITE_ENV_KEYS = [
8
5
  "VITE_APITOGO_DEV_PORTAL_API_URL",
9
- "VITE_APITOGO_DEV_PORTAL_MOCK_PLANS",
10
6
  ] as const;
11
7
 
12
- interface ManifestPlanInput {
13
- sku: string;
14
- displayName: string;
15
- isFree: boolean;
16
- priceAmount?: number;
17
- priceCurrency?: string;
18
- billingPeriod?: string;
19
- limitsJson?: string;
20
- }
21
-
22
- interface DevPortalLocalManifest {
23
- plans?: ManifestPlanInput[];
24
- }
25
-
26
- function normalizeBillingPeriod(period?: string): string {
27
- if (!period) {
28
- return "monthly";
29
- }
30
-
31
- const normalized = period.toLowerCase();
32
- if (normalized === "month") {
33
- return "monthly";
34
- }
35
- if (normalized === "year") {
36
- return "annual";
37
- }
38
- return normalized;
39
- }
40
-
41
- export function plansToMockEnvValue(
42
- plans: ManifestPlanInput[] | undefined,
43
- ): string | null {
44
- if (!plans?.length) {
45
- return null;
46
- }
47
-
48
- const mockPlans = plans.map((plan) => ({
49
- sku: plan.sku,
50
- displayName: plan.displayName,
51
- isFree: plan.isFree,
52
- priceAmount: plan.priceAmount ?? 0,
53
- priceCurrency: plan.priceCurrency ?? "usd",
54
- billingPeriod: normalizeBillingPeriod(plan.billingPeriod),
55
- limitsJson: plan.limitsJson ?? null,
56
- }));
57
-
58
- return JSON.stringify(mockPlans);
59
- }
60
-
61
- export async function readDevPortalManifest(
62
- rootDir: string,
63
- ): Promise<DevPortalLocalManifest | null> {
64
- const manifestPath = path.join(rootDir, DEV_PORTAL_MANIFEST_FILENAME);
65
-
66
- try {
67
- const raw = await readFile(manifestPath, "utf8");
68
- return JSON.parse(raw) as DevPortalLocalManifest;
69
- } catch {
70
- return null;
71
- }
72
- }
73
-
74
8
  export async function loadDevPortalViteDefines(
75
9
  rootDir: string,
76
10
  mode: string,
@@ -83,11 +17,5 @@ export async function loadDevPortalViteDefines(
83
17
  defines.VITE_APITOGO_DEV_PORTAL_API_URL = JSON.stringify(apiUrl);
84
18
  }
85
19
 
86
- const manifest = await readDevPortalManifest(rootDir);
87
- const mockPlansValue = plansToMockEnvValue(manifest?.plans);
88
- if (mockPlansValue) {
89
- defines.VITE_APITOGO_DEV_PORTAL_MOCK_PLANS = JSON.stringify(mockPlansValue);
90
- }
91
-
92
20
  return defines;
93
21
  }
@@ -0,0 +1,79 @@
1
+ import path from "node:path";
2
+ import type { Plugin, ResolvedConfig } from "vite";
3
+ import {
4
+ DEV_PORTAL_MANIFEST_FILENAME,
5
+ manifestFilePath,
6
+ parseLocalManifestJson,
7
+ plansToPreviewCatalog,
8
+ } from "../config/local-manifest.js";
9
+
10
+ export const APITOGO_LOCAL_MANIFEST_VIRTUAL_ID = "virtual:apitogo-local-manifest";
11
+ const resolvedVirtualModuleId = `\0${APITOGO_LOCAL_MANIFEST_VIRTUAL_ID}`;
12
+
13
+ const viteLocalManifestPlugin = (): Plugin => {
14
+ let rootDir = "";
15
+ let manifestPath = "";
16
+
17
+ const loadManifestModule = async (): Promise<string> => {
18
+ const { readFile } = await import("node:fs/promises");
19
+ let catalog = { plans: [] as ReturnType<typeof plansToPreviewCatalog>["plans"] };
20
+
21
+ try {
22
+ const raw = await readFile(manifestPath, "utf8");
23
+ const manifest = parseLocalManifestJson(raw);
24
+ catalog = plansToPreviewCatalog(manifest?.plans);
25
+ } catch {
26
+ // Missing or invalid manifest — empty catalog for preview.
27
+ }
28
+
29
+ return `export default ${JSON.stringify(catalog)};`;
30
+ };
31
+
32
+ return {
33
+ name: "apitogo-local-manifest-plugin",
34
+ configResolved(resolvedConfig: ResolvedConfig) {
35
+ rootDir = resolvedConfig.root;
36
+ manifestPath = manifestFilePath(rootDir);
37
+ },
38
+ resolveId(id) {
39
+ if (id === APITOGO_LOCAL_MANIFEST_VIRTUAL_ID) {
40
+ return resolvedVirtualModuleId;
41
+ }
42
+ },
43
+ async load(id) {
44
+ if (id !== resolvedVirtualModuleId) {
45
+ return;
46
+ }
47
+
48
+ return loadManifestModule();
49
+ },
50
+ configureServer(server) {
51
+ const watchPath = path.join(rootDir, DEV_PORTAL_MANIFEST_FILENAME);
52
+
53
+ server.watcher.add(watchPath);
54
+ server.watcher.on("change", async (changedPath) => {
55
+ if (path.resolve(changedPath) !== path.resolve(manifestPath)) {
56
+ return;
57
+ }
58
+
59
+ const module = server.moduleGraph.getModuleById(
60
+ resolvedVirtualModuleId,
61
+ );
62
+ if (module) {
63
+ server.moduleGraph.invalidateModule(module);
64
+ }
65
+
66
+ const mod = await server.moduleGraph.getModuleByUrl(
67
+ APITOGO_LOCAL_MANIFEST_VIRTUAL_ID,
68
+ );
69
+ if (mod) {
70
+ server.reloadModule(mod);
71
+ }
72
+
73
+ server.ws.send({ type: "full-reload", path: "*" });
74
+ });
75
+ },
76
+ };
77
+ };
78
+
79
+ export default viteLocalManifestPlugin;
@@ -17,12 +17,14 @@ import viteModulesPlugin from "./plugin-modules.js";
17
17
  import { viteNavigationPlugin } from "./plugin-navigation.js";
18
18
  import { viteSearchPlugin } from "./plugin-search.js";
19
19
  import { viteShikiRegisterPlugin } from "./plugin-shiki-register.js";
20
+ import viteLocalManifestPlugin from "./plugin-local-manifest.js";
20
21
  import { viteThemePlugin } from "./plugin-theme.js";
21
22
 
22
23
  export default function vitePlugin(): PluginOption {
23
24
  return [
24
25
  viteShikiRegisterPlugin(),
25
26
  viteConfigReloadPlugin(),
27
+ viteLocalManifestPlugin(),
26
28
  viteMdxPlugin(),
27
29
  react({ include: /\.(mdx?|jsx?|tsx?)$/ }),
28
30
  viteConfigPlugin(),