@lukoweb/apitogo 0.1.63 → 0.1.65

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 CHANGED
@@ -187,26 +187,58 @@ function parseEnvValue(raw) {
187
187
  }
188
188
  return trimmed;
189
189
  }
190
+ function screamingSnakeToCamel(segment) {
191
+ return segment.toLowerCase().split("_").filter(Boolean).map(
192
+ (part, index) => index === 0 ? part : `${part.charAt(0).toUpperCase()}${part.slice(1)}`
193
+ ).join("");
194
+ }
195
+ function normalizeEnvPathSegment(segment) {
196
+ if (!segment) {
197
+ return segment;
198
+ }
199
+ const isAllUpper = segment === segment.toUpperCase() && /[A-Z]/.test(segment);
200
+ if (!isAllUpper) {
201
+ return segment;
202
+ }
203
+ if (segment.includes("_")) {
204
+ return screamingSnakeToCamel(segment);
205
+ }
206
+ return COLLAPSED_UPPERCASE_ALIASES[segment] ?? segment.toLowerCase();
207
+ }
208
+ function resolveExistingKey(target, key) {
209
+ if (Object.hasOwn(target, key)) {
210
+ return key;
211
+ }
212
+ const lower = key.toLowerCase();
213
+ for (const existing of Object.keys(target)) {
214
+ if (existing.toLowerCase() === lower) {
215
+ return existing;
216
+ }
217
+ }
218
+ return key;
219
+ }
190
220
  function setNestedValue(target, pathParts, value) {
191
221
  if (pathParts.length === 0) {
192
222
  return;
193
223
  }
194
224
  let current = target;
195
225
  for (let index = 0; index < pathParts.length - 1; index += 1) {
196
- const key = pathParts[index];
197
- if (!key) {
226
+ const rawKey = pathParts[index];
227
+ if (!rawKey) {
198
228
  return;
199
229
  }
230
+ const key = resolveExistingKey(current, rawKey);
200
231
  const next = current[key];
201
232
  if (next === void 0 || next === null || typeof next !== "object" || Array.isArray(next)) {
202
233
  current[key] = {};
203
234
  }
204
235
  current = current[key];
205
236
  }
206
- const leafKey = pathParts[pathParts.length - 1];
207
- if (!leafKey) {
237
+ const rawLeaf = pathParts[pathParts.length - 1];
238
+ if (!rawLeaf) {
208
239
  return;
209
240
  }
241
+ const leafKey = resolveExistingKey(current, rawLeaf);
210
242
  current[leafKey] = value;
211
243
  }
212
244
  function stripRuntimeConfigFields(config2) {
@@ -219,7 +251,7 @@ function applyEnvOverrides(config2, env = process.env) {
219
251
  if (!key.startsWith(ENV_PREFIX) || value === void 0) {
220
252
  continue;
221
253
  }
222
- const pathParts = key.slice(ENV_PREFIX.length).split("__").filter(Boolean);
254
+ const pathParts = key.slice(ENV_PREFIX.length).split("__").filter(Boolean).map(normalizeEnvPathSegment);
223
255
  if (pathParts.length === 0) {
224
256
  continue;
225
257
  }
@@ -550,7 +582,7 @@ function extractSiteConfig(config2) {
550
582
  }
551
583
  return siteConfig;
552
584
  }
553
- var APITOGO_CONFIG_FILENAME, ENV_PREFIX, DERIVED_MANIFEST_KEYS, SITE_CONFIG_KEYS;
585
+ var APITOGO_CONFIG_FILENAME, ENV_PREFIX, DERIVED_MANIFEST_KEYS, SITE_CONFIG_KEYS, COLLAPSED_UPPERCASE_ALIASES;
554
586
  var init_apitogo_config_core = __esm({
555
587
  "src/config/apitogo-config-core.ts"() {
556
588
  APITOGO_CONFIG_FILENAME = "apitogo.config.json";
@@ -583,6 +615,28 @@ var init_apitogo_config_core = __esm({
583
615
  "versions",
584
616
  "options"
585
617
  ];
618
+ COLLAPSED_UPPERCASE_ALIASES = {
619
+ SECRETKEY: "secretKey",
620
+ WEBHOOKSECRET: "webhookSecret",
621
+ APIBASEURL: "apiBaseUrl",
622
+ CLIENTID: "clientId",
623
+ CLIENTSECRET: "clientSecret",
624
+ CONNECTIONSTRING: "connectionString",
625
+ DEMOMODE: "demoMode",
626
+ DISPLAYNAME: "displayName",
627
+ PRICEAMOUNT: "priceAmount",
628
+ PRICECURRENCY: "priceCurrency",
629
+ BILLINGPERIOD: "billingPeriod",
630
+ LIMITSJSON: "limitsJson",
631
+ INCLUDEDACTIONKEYS: "includedActionKeys",
632
+ SOURCEDIRECTORY: "sourceDirectory",
633
+ PUBLISHDIRECTORY: "publishDirectory",
634
+ OPENAPIPATH: "openApiPath",
635
+ RUNTIMEVERSION: "runtimeVersion",
636
+ PROJECTID: "projectId",
637
+ SITENAME: "siteName",
638
+ LOGOURL: "logoUrl"
639
+ };
586
640
  }
587
641
  });
588
642
 
@@ -852,7 +906,10 @@ var init_ModulesSchema = __esm({
852
906
  sku: z3.string(),
853
907
  displayName: z3.string(),
854
908
  priceAmount: z3.number().default(0),
855
- priceCurrency: z3.string().trim().toLowerCase().regex(/^[a-z]{3}$/, "priceCurrency must be a 3-letter ISO 4217 code (lowercase)").optional(),
909
+ priceCurrency: z3.string().trim().toLowerCase().regex(
910
+ /^[a-z]{3}$/,
911
+ "priceCurrency must be a 3-letter ISO 4217 code (lowercase)"
912
+ ).optional(),
856
913
  billingPeriod: z3.string().optional(),
857
914
  limitsJson: z3.string().optional(),
858
915
  includedActionKeys: z3.array(z3.string()).optional()
@@ -999,7 +1056,10 @@ var init_local_manifest = __esm({
999
1056
  init_apitogo_config_loader();
1000
1057
  init_landing_manifest();
1001
1058
  BillingPeriodSchema = z4.enum(["month", "year", "monthly", "annual"]);
1002
- PriceCurrencySchema = z4.string().trim().toLowerCase().regex(/^[a-z]{3}$/, "priceCurrency must be a 3-letter ISO 4217 code (lowercase)");
1059
+ PriceCurrencySchema = z4.string().trim().toLowerCase().regex(
1060
+ /^[a-z]{3}$/,
1061
+ "priceCurrency must be a 3-letter ISO 4217 code (lowercase)"
1062
+ );
1003
1063
  ALLOWED_RUNTIME_VERSIONS = [
1004
1064
  "DOTNET_10",
1005
1065
  "DOTNET_9",
@@ -1057,7 +1117,9 @@ var init_local_manifest = __esm({
1057
1117
  openApiPath: z4.string().optional(),
1058
1118
  runtime: z4.enum(["dotnet", "nodejs", "python"]).or(z4.string()).optional(),
1059
1119
  runtimeVersion: z4.string().optional().refine(
1060
- (value) => value === void 0 || ALLOWED_RUNTIME_VERSIONS.includes(value.toUpperCase()) || /^(8(\.0)?|9(\.0)?|10(\.0)?|net8\.0|net9\.0|net10\.0|24|22|3\.(10|11|12|13|14))$/i.test(
1120
+ (value) => value === void 0 || ALLOWED_RUNTIME_VERSIONS.includes(
1121
+ value.toUpperCase()
1122
+ ) || /^(8(\.0)?|9(\.0)?|10(\.0)?|net8\.0|net9\.0|net10\.0|24|22|3\.(10|11|12|13|14))$/i.test(
1061
1123
  value
1062
1124
  ),
1063
1125
  {
@@ -5273,7 +5335,7 @@ import {
5273
5335
  // package.json
5274
5336
  var package_default = {
5275
5337
  name: "@lukoweb/apitogo",
5276
- version: "0.1.63",
5338
+ version: "0.1.65",
5277
5339
  type: "module",
5278
5340
  sideEffects: [
5279
5341
  "**/*.css",
@@ -19,6 +19,7 @@ export type AuthenticationSecrets = {
19
19
  webhookSecret?: string;
20
20
  };
21
21
  };
22
+ export declare function normalizeEnvPathSegment(segment: string): string;
22
23
  export declare function applyEnvOverrides(config: ApitogoJsonConfig, env?: NodeJS.ProcessEnv): ApitogoJsonConfig;
23
24
  type PlanCatalogItem = Record<string, unknown> & {
24
25
  sku?: string;
@@ -45,7 +45,7 @@ VITE_APITOGO_AUTHENTICATION_API_BASE_URL=https://your-dev-portal-api.example.com
45
45
  ```
46
46
 
47
47
  You can also set `authentication.apiBaseUrl` in `apitogo.config.json` or
48
- `APITOGO_CONFIG_authentication__apiBaseUrl` in `.env`.
48
+ `APITOGO_CONFIG_AUTHENTICATION__API_BASE_URL` in `.env`.
49
49
 
50
50
  **Local demo mode:** In development (`npm run dev`), `demoMode` is enabled by default when the user
51
51
  panel is on. You do not need `VITE_APITOGO_AUTHENTICATION_API_BASE_URL`, OIDC, or a running
@@ -55,7 +55,7 @@ dev-portal backend to explore protected account pages locally. Sign in at `/logi
55
55
  demo@apitogo.local / password
56
56
  ```
57
57
 
58
- Disable with `APITOGO_CONFIG_demoMode=false` in `.env` or `"demoMode": false` in
58
+ Disable with `APITOGO_CONFIG_DEMO_MODE=false` in `.env` or `"demoMode": false` in
59
59
  `apitogo.config.json`. Demo mode is never active in production builds.
60
60
 
61
61
  **Local preview (demo off):** When the backend URL is a placeholder or unreachable and demo mode is
@@ -70,7 +70,7 @@ without restarting the dev server.
70
70
  | -------------------------------------------------------- | -------------------------------------------------------------------------- |
71
71
  | Site, plans, modules, branding, backend/gateway metadata | `apitogo.config.json` (+ `.env` overrides — see [Manifest](./manifest.md)) |
72
72
  | Plugins and custom code | `apitogo.config.tsx` |
73
- | OIDC and Stripe for publish | `.env.local` via `APITOGO_CONFIG_authentication__*` |
73
+ | OIDC and Stripe for publish | `.env` via `APITOGO_CONFIG_AUTHENTICATION__*` |
74
74
 
75
75
  **Production:** Register one OIDC app with redirect URI `https://{backend}/signin-oidc` (see MCP
76
76
  `manualSteps.oidcRedirectUri`). The frontend uses cookie sessions via `credentials: "include"` on
@@ -9,7 +9,7 @@ metadata. Code-only pieces (plugins, custom components) stay in `apitogo.config.
9
9
  | --------------------- | ----------------------------------------------------------- |
10
10
  | `apitogo.config.json` | Committed defaults: site, modules, authentication, backend |
11
11
  | `apitogo.config.tsx` | Plugins and other non-JSON configuration |
12
- | `.env` / `.env.local` | Public client vars + publish secrets via `APITOGO_CONFIG_*` |
12
+ | `.env` | Public client vars + publish secrets via `APITOGO_CONFIG_*` |
13
13
 
14
14
  ## Canonical shape
15
15
 
@@ -70,23 +70,23 @@ Public client URL (mirrors `authentication.apiBaseUrl`):
70
70
  VITE_APITOGO_AUTHENTICATION_API_BASE_URL=https://your-dev-portal-api.example.com
71
71
  ```
72
72
 
73
- Nested config overrides:
73
+ Nested config overrides (uppercase SCREAMING_SNAKE path segments; camelCase still accepted):
74
74
 
75
75
  ```sh
76
- APITOGO_CONFIG_site__title=My Local Site
77
- APITOGO_CONFIG_pricing__enabled=true
78
- APITOGO_CONFIG_projectId=your-local-project-id
79
- APITOGO_CONFIG_authentication__apiBaseUrl=https://your-dev-portal-api.example.com
76
+ APITOGO_CONFIG_SITE__TITLE=My Local Site
77
+ APITOGO_CONFIG_PRICING__ENABLED=true
78
+ APITOGO_CONFIG_PROJECT_ID=your-local-project-id
79
+ APITOGO_CONFIG_AUTHENTICATION__API_BASE_URL=https://your-dev-portal-api.example.com
80
80
  ```
81
81
 
82
- Publish secrets (`.env.local`, never commit):
82
+ Publish secrets (uncomment in `.env`, never commit):
83
83
 
84
84
  ```sh
85
- APITOGO_CONFIG_authentication__oidc__authority=https://login.example.com
86
- APITOGO_CONFIG_authentication__oidc__clientId=your-client-id
87
- APITOGO_CONFIG_authentication__oidc__clientSecret=your-client-secret
88
- APITOGO_CONFIG_authentication__stripe__secretKey=sk_test_...
89
- APITOGO_CONFIG_authentication__stripe__webhookSecret=whsec_...
85
+ APITOGO_CONFIG_AUTHENTICATION__OIDC__AUTHORITY=https://login.example.com
86
+ APITOGO_CONFIG_AUTHENTICATION__OIDC__CLIENT_ID=your-client-id
87
+ APITOGO_CONFIG_AUTHENTICATION__OIDC__CLIENT_SECRET=your-client-secret
88
+ APITOGO_CONFIG_AUTHENTICATION__STRIPE__SECRET_KEY=sk_test_...
89
+ APITOGO_CONFIG_AUTHENTICATION__STRIPE__WEBHOOK_SECRET=whsec_...
90
90
  ```
91
91
 
92
92
  ## Thin `apitogo.config.tsx`
@@ -26,7 +26,7 @@ environment-specific variables. See the
26
26
  [Vite documentation](https://vite.dev/guide/env-and-mode.html#env-files) for more information on
27
27
  supported files.
28
28
 
29
- Here is an example of a `.env.local` file:
29
+ Here is an example of a `.env` file:
30
30
 
31
31
  ```sh
32
32
  APITOGO_PUBLIC_PAGE_TITLE=My Page Title
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lukoweb/apitogo",
3
- "version": "0.1.63",
3
+ "version": "0.1.65",
4
4
  "type": "module",
5
5
  "sideEffects": [
6
6
  "**/*.css",
@@ -47,7 +47,7 @@ export type AuthenticationSecrets = {
47
47
  email?: {
48
48
  connectionString?: string;
49
49
  };
50
- /** Provider secrets keyed by provider `id` (from env: `authentication__providers__{id}__clientSecret`). */
50
+ /** Provider secrets keyed by provider `id` (from env: `AUTHENTICATION__PROVIDERS__{id}__CLIENT_SECRET`). */
51
51
  providers?: Record<string, { clientSecret?: string }>;
52
52
  stripe?: {
53
53
  secretKey?: string;
@@ -89,6 +89,81 @@ function parseEnvValue(raw: string | undefined): unknown {
89
89
  return trimmed;
90
90
  }
91
91
 
92
+ /** Collapsed ALLCAPS aliases for multi-word camelCase config keys. */
93
+ const COLLAPSED_UPPERCASE_ALIASES: Record<string, string> = {
94
+ SECRETKEY: "secretKey",
95
+ WEBHOOKSECRET: "webhookSecret",
96
+ APIBASEURL: "apiBaseUrl",
97
+ CLIENTID: "clientId",
98
+ CLIENTSECRET: "clientSecret",
99
+ CONNECTIONSTRING: "connectionString",
100
+ DEMOMODE: "demoMode",
101
+ DISPLAYNAME: "displayName",
102
+ PRICEAMOUNT: "priceAmount",
103
+ PRICECURRENCY: "priceCurrency",
104
+ BILLINGPERIOD: "billingPeriod",
105
+ LIMITSJSON: "limitsJson",
106
+ INCLUDEDACTIONKEYS: "includedActionKeys",
107
+ SOURCEDIRECTORY: "sourceDirectory",
108
+ PUBLISHDIRECTORY: "publishDirectory",
109
+ OPENAPIPATH: "openApiPath",
110
+ RUNTIMEVERSION: "runtimeVersion",
111
+ PROJECTID: "projectId",
112
+ SITENAME: "siteName",
113
+ LOGOURL: "logoUrl",
114
+ };
115
+
116
+ function screamingSnakeToCamel(segment: string): string {
117
+ return segment
118
+ .toLowerCase()
119
+ .split("_")
120
+ .filter(Boolean)
121
+ .map((part, index) =>
122
+ index === 0 ? part : `${part.charAt(0).toUpperCase()}${part.slice(1)}`,
123
+ )
124
+ .join("");
125
+ }
126
+
127
+ /**
128
+ * Normalize one `APITOGO_CONFIG_*` path segment to the JSON key casing.
129
+ * - Mixed/camelCase → leave as-is (legacy)
130
+ * - `SECRET_KEY` → `secretKey`
131
+ * - `AUTHENTICATION` → `authentication`
132
+ * - `SECRETKEY` → `secretKey` (collapsed alias)
133
+ */
134
+ export function normalizeEnvPathSegment(segment: string): string {
135
+ if (!segment) {
136
+ return segment;
137
+ }
138
+
139
+ const isAllUpper = segment === segment.toUpperCase() && /[A-Z]/.test(segment);
140
+ if (!isAllUpper) {
141
+ return segment;
142
+ }
143
+
144
+ if (segment.includes("_")) {
145
+ return screamingSnakeToCamel(segment);
146
+ }
147
+
148
+ return COLLAPSED_UPPERCASE_ALIASES[segment] ?? segment.toLowerCase();
149
+ }
150
+
151
+ function resolveExistingKey(
152
+ target: Record<string, unknown>,
153
+ key: string,
154
+ ): string {
155
+ if (Object.hasOwn(target, key)) {
156
+ return key;
157
+ }
158
+ const lower = key.toLowerCase();
159
+ for (const existing of Object.keys(target)) {
160
+ if (existing.toLowerCase() === lower) {
161
+ return existing;
162
+ }
163
+ }
164
+ return key;
165
+ }
166
+
92
167
  function setNestedValue(
93
168
  target: Record<string, unknown>,
94
169
  pathParts: string[],
@@ -100,10 +175,11 @@ function setNestedValue(
100
175
 
101
176
  let current: Record<string, unknown> = target;
102
177
  for (let index = 0; index < pathParts.length - 1; index += 1) {
103
- const key = pathParts[index];
104
- if (!key) {
178
+ const rawKey = pathParts[index];
179
+ if (!rawKey) {
105
180
  return;
106
181
  }
182
+ const key = resolveExistingKey(current, rawKey);
107
183
  const next = current[key];
108
184
  if (
109
185
  next === undefined ||
@@ -116,11 +192,12 @@ function setNestedValue(
116
192
  current = current[key] as Record<string, unknown>;
117
193
  }
118
194
 
119
- const leafKey = pathParts[pathParts.length - 1];
120
- if (!leafKey) {
195
+ const rawLeaf = pathParts[pathParts.length - 1];
196
+ if (!rawLeaf) {
121
197
  return;
122
198
  }
123
199
 
200
+ const leafKey = resolveExistingKey(current, rawLeaf);
124
201
  current[leafKey] = value;
125
202
  }
126
203
 
@@ -144,7 +221,11 @@ export function applyEnvOverrides(
144
221
  continue;
145
222
  }
146
223
 
147
- const pathParts = key.slice(ENV_PREFIX.length).split("__").filter(Boolean);
224
+ const pathParts = key
225
+ .slice(ENV_PREFIX.length)
226
+ .split("__")
227
+ .filter(Boolean)
228
+ .map(normalizeEnvPathSegment);
148
229
 
149
230
  if (pathParts.length === 0) {
150
231
  continue;
@@ -32,7 +32,10 @@ const PriceCurrencySchema = z
32
32
  .string()
33
33
  .trim()
34
34
  .toLowerCase()
35
- .regex(/^[a-z]{3}$/, "priceCurrency must be a 3-letter ISO 4217 code (lowercase)");
35
+ .regex(
36
+ /^[a-z]{3}$/,
37
+ "priceCurrency must be a 3-letter ISO 4217 code (lowercase)",
38
+ );
36
39
 
37
40
  export const ALLOWED_RUNTIME_VERSIONS = [
38
41
  "DOTNET_10",
@@ -116,7 +119,9 @@ export const DevPortalLocalManifestSchema = z.object({
116
119
  .refine(
117
120
  (value) =>
118
121
  value === undefined ||
119
- (ALLOWED_RUNTIME_VERSIONS as readonly string[]).includes(value.toUpperCase()) ||
122
+ (ALLOWED_RUNTIME_VERSIONS as readonly string[]).includes(
123
+ value.toUpperCase(),
124
+ ) ||
120
125
  /^(8(\.0)?|9(\.0)?|10(\.0)?|net8\.0|net9\.0|net10\.0|24|22|3\.(10|11|12|13|14))$/i.test(
121
126
  value,
122
127
  ),
@@ -266,7 +266,10 @@ const PlanCatalogItemSchema = z.object({
266
266
  .string()
267
267
  .trim()
268
268
  .toLowerCase()
269
- .regex(/^[a-z]{3}$/, "priceCurrency must be a 3-letter ISO 4217 code (lowercase)")
269
+ .regex(
270
+ /^[a-z]{3}$/,
271
+ "priceCurrency must be a 3-letter ISO 4217 code (lowercase)",
272
+ )
270
273
  .optional(),
271
274
  billingPeriod: z.string().optional(),
272
275
  limitsJson: z.string().optional(),