@lukoweb/apitogo 0.1.92 → 0.1.94

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
@@ -301,23 +301,15 @@ function firstApiInput(config2) {
301
301
  }
302
302
  return void 0;
303
303
  }
304
- function mergeAuthenticationGateway(config2) {
304
+ function normalizeGatewayPlacement(config2) {
305
305
  const authentication = asRecord2(config2.authentication);
306
306
  if (!authentication) {
307
- return void 0;
307
+ return;
308
308
  }
309
- const authGateway = asRecord2(authentication.gateway);
310
- const rootGateway = asRecord2(config2.gateway);
311
- if (!authGateway && !rootGateway) {
312
- return authentication;
309
+ if ("gateway" in authentication) {
310
+ delete authentication.gateway;
311
+ config2.authentication = authentication;
313
312
  }
314
- return {
315
- ...authentication,
316
- gateway: {
317
- ...rootGateway,
318
- ...authGateway
319
- }
320
- };
321
313
  }
322
314
  function ensureModules(config2) {
323
315
  const modules = asRecord2(config2.modules) ?? {};
@@ -378,11 +370,10 @@ function normalizeApitogoConfig(config2) {
378
370
  };
379
371
  }
380
372
  normalized.site = site;
381
- const authentication = mergeAuthenticationGateway(normalized);
373
+ const authentication = asRecord2(normalized.authentication);
382
374
  if (authentication) {
383
- normalized.authentication = authentication;
375
+ normalizeGatewayPlacement(normalized);
384
376
  }
385
- delete normalized.gateway;
386
377
  const modules = ensureModules(normalized);
387
378
  const legacyDocs = asRecord2(normalized.docs);
388
379
  const moduleDocs = asRecord2(modules.docs) ?? {};
@@ -533,13 +524,16 @@ function deriveManifestFields(config2) {
533
524
  continue;
534
525
  }
535
526
  if (key === "authentication") {
536
- const authentication2 = asRecord2(normalized.authentication);
537
- if (authentication2) {
538
- manifest.authentication = stripAuthenticationSecrets2(authentication2);
527
+ const authentication = asRecord2(normalized.authentication);
528
+ if (authentication) {
529
+ manifest.authentication = stripAuthenticationSecrets2(authentication);
539
530
  }
540
531
  continue;
541
532
  }
542
533
  if (key === "gateway") {
534
+ if (normalized.gateway !== void 0) {
535
+ manifest.gateway = normalized.gateway;
536
+ }
543
537
  continue;
544
538
  }
545
539
  if (key === "pricing") {
@@ -570,10 +564,6 @@ function deriveManifestFields(config2) {
570
564
  manifest[key] = normalized[key];
571
565
  }
572
566
  }
573
- const authentication = asRecord2(normalized.authentication);
574
- if (authentication?.gateway) {
575
- manifest.gateway = authentication.gateway;
576
- }
577
567
  return manifest;
578
568
  }
579
569
  function extractManifest(config2) {
@@ -1158,7 +1148,11 @@ var init_local_manifest = __esm({
1158
1148
  oidcAllowedIssuers: z4.array(z4.string()).optional(),
1159
1149
  oidcAudiences: z4.array(z4.string()).optional(),
1160
1150
  limitsJson: z4.string().optional(),
1161
- publicHealthActionKey: z4.string().optional()
1151
+ publicHealthActionKey: z4.string().optional(),
1152
+ publicRoutes: z4.array(z4.string()).optional(),
1153
+ cors: z4.object({
1154
+ allowedOrigins: z4.array(z4.string()).optional()
1155
+ }).optional()
1162
1156
  }).optional(),
1163
1157
  projectId: z4.string().optional(),
1164
1158
  landing: z4.object({
@@ -6034,6 +6028,7 @@ function manifestFilePath(siteDirectory) {
6034
6028
  var STORED_MANIFEST_KEYS = [
6035
6029
  "authentication",
6036
6030
  "backend",
6031
+ "gateway",
6037
6032
  "projectId"
6038
6033
  ];
6039
6034
  function readPlanItemsFromConfig(config2) {
@@ -6189,12 +6184,6 @@ function extractManifestFromConfig(config2) {
6189
6184
  if (readPricingEnabledFromConfig(config2)) {
6190
6185
  manifest.pricing = { enabled: true };
6191
6186
  }
6192
- const authConfig = config2.authentication;
6193
- if (authConfig?.gateway) {
6194
- manifest.gateway = authConfig.gateway;
6195
- } else if (config2.gateway) {
6196
- manifest.gateway = config2.gateway;
6197
- }
6198
6187
  const landing = readLandingFromConfig(config2);
6199
6188
  if (landing) {
6200
6189
  manifest.landing = landing;
@@ -6234,15 +6223,14 @@ function applyManifestToConfig(config2, manifest) {
6234
6223
  }
6235
6224
  delete next.plans;
6236
6225
  delete next.pricing;
6237
- if (manifest.gateway) {
6238
- const authentication = next.authentication ?? {};
6239
- authentication.gateway = manifest.gateway;
6226
+ if (next.authentication && typeof next.authentication === "object") {
6227
+ const authentication = { ...next.authentication };
6228
+ delete authentication.gateway;
6240
6229
  next.authentication = authentication;
6241
6230
  }
6242
6231
  delete next.siteName;
6243
6232
  delete next.branding;
6244
6233
  delete next.projectName;
6245
- delete next.gateway;
6246
6234
  return next;
6247
6235
  }
6248
6236
  async function readLegacyManifest(_siteDirectory) {
@@ -9407,7 +9395,8 @@ var PLATFORM_PLAN_CATALOG = [
9407
9395
  "Paid end-user gateway plans (Stripe checkout for API subscribers)",
9408
9396
  "1 custom domain on the dev portal",
9409
9397
  "100k requests included; up to 1M requests / month",
9410
- "Stripe subscription billing with promotion codes at checkout"
9398
+ "Stripe subscription billing with promotion codes at checkout",
9399
+ "Standard support"
9411
9400
  ],
9412
9401
  limitations: [
9413
9402
  "Requires Stripe checkout to activate",
@@ -9427,7 +9416,8 @@ var PLATFORM_PLAN_CATALOG = [
9427
9416
  "3 custom domains",
9428
9417
  "1M requests included; up to 10M requests / month",
9429
9418
  "Advanced analytics and CDN for your API",
9430
- "Extended monitoring history and alerts"
9419
+ "Extended monitoring history and alerts",
9420
+ "Enterprise support"
9431
9421
  ],
9432
9422
  limitations: [
9433
9423
  "Requires Stripe checkout to activate",
@@ -9446,8 +9436,8 @@ function buildPlatformPlanSelectionAgentInstruction(quota) {
9446
9436
  const lines = [
9447
9437
  "Before calling ensure_platform_plan, explain the platform hosting tiers to the user using platformPlanCatalog from the bootstrap response or list_platform_plans.",
9448
9438
  "Free: $0/month, dev portal + API hub, no custom domain, free ($0) gateway/API plans only.",
9449
- "Starter: $99/month via Stripe, custom domain, paid gateway plans, production hosting.",
9450
- "Professional: $999/month via Stripe, higher limits, multiple domains, advanced analytics.",
9439
+ "Starter: $99/month via Stripe, custom domain, paid gateway plans, production hosting, standard support.",
9440
+ "Professional: $999/month via Stripe, higher limits, multiple domains, advanced analytics, enterprise support.",
9451
9441
  "This is separate from gateway/API plans (what end users pay for API access) configured in apitogo.config.json.",
9452
9442
  "If apitogo.config.json already has paid gateway plans (priceAmount > 0), warn before they pick Free: Free cannot publish paid API plans \u2014 they must choose Starter/Professional, or remove/zero those paid plans first. Never silently strip paid plans."
9453
9443
  ];
@@ -12545,7 +12535,7 @@ import {
12545
12535
  // package.json
12546
12536
  var package_default = {
12547
12537
  name: "@lukoweb/apitogo",
12548
- version: "0.1.92",
12538
+ version: "0.1.94",
12549
12539
  type: "module",
12550
12540
  sideEffects: [
12551
12541
  "**/*.css",
@@ -18516,12 +18506,13 @@ function buildCreateApitogoCliArgs(invocationArgs, projectName, options) {
18516
18506
  const cliArgs = [
18517
18507
  ...invocationArgs,
18518
18508
  projectName,
18519
- "--yes",
18509
+ "--typescript",
18510
+ "--eslint",
18520
18511
  "--skip-install",
18521
18512
  "--use-npm"
18522
18513
  ];
18523
18514
  if (options?.withExamples) {
18524
- cliArgs.push("--with-examples");
18515
+ cliArgs.push("--include-examples");
18525
18516
  }
18526
18517
  return cliArgs;
18527
18518
  }
@@ -58,6 +58,9 @@ export interface DevPortalLocalManifest {
58
58
  limitsJson?: string;
59
59
  publicHealthActionKey?: string;
60
60
  publicRoutes?: string[];
61
+ cors?: {
62
+ allowedOrigins?: string[];
63
+ };
61
64
  };
62
65
  projectId?: string;
63
66
  landing?: LandingManifestInput;
@@ -89,6 +89,10 @@ export declare const DevPortalLocalManifestSchema: z.ZodObject<{
89
89
  oidcAudiences: z.ZodOptional<z.ZodArray<z.ZodString>>;
90
90
  limitsJson: z.ZodOptional<z.ZodString>;
91
91
  publicHealthActionKey: z.ZodOptional<z.ZodString>;
92
+ publicRoutes: z.ZodOptional<z.ZodArray<z.ZodString>>;
93
+ cors: z.ZodOptional<z.ZodObject<{
94
+ allowedOrigins: z.ZodOptional<z.ZodArray<z.ZodString>>;
95
+ }, z.core.$strip>>;
92
96
  }, z.core.$strip>>;
93
97
  projectId: z.ZodOptional<z.ZodString>;
94
98
  landing: z.ZodOptional<z.ZodObject<{
@@ -29,7 +29,8 @@ same commands so agents and humans get identical behavior.
29
29
  | `init [dir]` | Named folder; `--install` (default), `--overwrite`, `--json`, `--openapi-search` |
30
30
  | `config` | Interactive on TTY; flags: `--openapi`, `--page-*`, `--plan`, `--backend`, `--theme`, `--patch`, `--json` |
31
31
 
32
- `npm create @lukoweb/apitogo@latest` remains an interactive alternative for new folders.
32
+ `npx @lukoweb/create-apitogo@latest` remains an interactive alternative for new folders.
33
+ Pass `--typescript --eslint` to skip those prompts. See [Install the CLI](./setup.md#scaffold-options).
33
34
 
34
35
  See [Make config (CLI)](../configuration/make-config.mdx) for all `config` flags.
35
36
 
@@ -13,7 +13,7 @@ local files, preview with `apitogo dev`, and publish to APItoGo hosting with `ap
13
13
 
14
14
  ## What you get
15
15
 
16
- - Scaffold a local portal site (`npm create @lukoweb/apitogo@latest` or `apitogo init`)
16
+ - Scaffold a local portal site (`npx @lukoweb/create-apitogo@latest` or `apitogo init`)
17
17
  - Shape backend, OpenAPI, plans, and docs pages via `apitogo config` and file edits
18
18
  - Preview locally (`apitogo dev`)
19
19
  - Publish to APItoGo hosting (`apitogo apply`) with platform plans **Free**, **Starter**, or
package/docs/cli/setup.md CHANGED
@@ -23,10 +23,10 @@ need a global install — scaffold commands add it to your project and `npm inst
23
23
 
24
24
  ### New project (recommended)
25
25
 
26
- `npm create` scaffolds the site **and** installs `@lukoweb/apitogo` locally:
26
+ `npx @lukoweb/create-apitogo` scaffolds the site **and** installs `@lukoweb/apitogo` locally:
27
27
 
28
28
  ```bash
29
- npm create @lukoweb/apitogo@latest
29
+ npx @lukoweb/create-apitogo@latest
30
30
  cd your-project-name
31
31
  npx apitogo config # interactive: backend, OpenAPI, pages, plans
32
32
  npm run dev
@@ -35,14 +35,28 @@ npm run dev
35
35
  Also works with `pnpm create @lukoweb/apitogo`, `yarn create @lukoweb/apitogo`, or
36
36
  `bunx @lukoweb/create-apitogo`.
37
37
 
38
- Non-interactive example:
38
+ Non-interactive:
39
39
 
40
40
  ```bash
41
- npm create @lukoweb/apitogo@latest my-portal -- --yes --typescript
41
+ npx @lukoweb/create-apitogo@latest my-portal --typescript --eslint
42
42
  cd my-portal
43
43
  npx apitogo config
44
44
  ```
45
45
 
46
+ #### Scaffold options
47
+
48
+ | Flag | Effect |
49
+ | ---- | ------ |
50
+ | `--typescript` / `--ts` | TypeScript project |
51
+ | `--javascript` / `--js` | JavaScript project |
52
+ | `--eslint` | Add ESLint config |
53
+ | `--no-eslint` | Skip ESLint |
54
+ | `--include-examples` | Add starter docs and a sample OpenAPI spec (off unless passed) |
55
+
56
+ Omit flags to be prompted for TypeScript and ESLint. Example files are never prompted.
57
+
58
+ See `@lukoweb/create-apitogo --help` for package manager and install flags.
59
+
46
60
  ### Scaffold into an existing folder
47
61
 
48
62
  `apitogo init` writes `package.json` with `@lukoweb/apitogo` as a dependency. Run **`npm install`**
@@ -63,7 +77,7 @@ to preview, then `apitogo apply` when ready to go live.
63
77
 
64
78
  | Situation | Command |
65
79
  | --------- | ------- |
66
- | After `npm create` or `npm install` in the project | `npx apitogo <command>` (e.g. `npx apitogo config`) |
80
+ | After `npx @lukoweb/create-apitogo` or `npm install` in the project | `npx apitogo <command>` (e.g. `npx apitogo config`) |
67
81
  | Same, if `node_modules/.bin` is on your PATH | `apitogo <command>` |
68
82
  | One-off, no project yet | `npx @lukoweb/apitogo <command>` |
69
83
  | Optional global install | `npm install -g @lukoweb/apitogo` then `apitogo <command>` anywhere |
package/docs/cli/shape.md CHANGED
@@ -13,10 +13,10 @@ Requires the **`apitogo`** CLI (`@lukoweb/apitogo`). If you have not installed i
13
13
 
14
14
  ## Happy path
15
15
 
16
- `npm create` installs `@lukoweb/apitogo` into the new project. Then run config via `npx`:
16
+ `npx @lukoweb/create-apitogo` installs `@lukoweb/apitogo` into the new project. Then run config via `npx`:
17
17
 
18
18
  ```bash
19
- npm create @lukoweb/apitogo@latest my-portal -- --yes
19
+ npx @lukoweb/create-apitogo@latest my-portal --typescript --eslint
20
20
  cd my-portal
21
21
  npx apitogo config # backend, OpenAPI, pages, plans (apply each immediately)
22
22
  npm run dev # http://localhost:3000
@@ -20,7 +20,7 @@ on new scaffolds. The command does **not** change `redirects`.
20
20
 
21
21
  Install the CLI first — see [Install the CLI](../cli/setup.md). In short:
22
22
 
23
- - **New site:** `npm create @lukoweb/apitogo@latest` (installs `@lukoweb/apitogo` into the project)
23
+ - **New site:** `npx @lukoweb/create-apitogo@latest` (installs `@lukoweb/apitogo` into the project)
24
24
  - **Existing folder:** `npx @lukoweb/apitogo init .` then `npm install`
25
25
 
26
26
  Run config from the project root with `npx apitogo config` (or `apitogo config` if
@@ -25,10 +25,17 @@ metadata, with optional `.env` overrides for secrets and local tweaks.
25
25
  "authentication": {
26
26
  "enabled": true,
27
27
  "type": "dev-portal",
28
- "apiBaseUrl": "https://your-dev-portal-api.example.com",
29
- "gateway": {
30
- "authMode": "bringYourOwnOidc",
31
- "oidcMetadataUrl": "https://login.example.com/.well-known/openid-configuration"
28
+ "apiBaseUrl": "https://your-dev-portal-api.example.com"
29
+ },
30
+ "gateway": {
31
+ "authMode": "bringYourOwnOidc",
32
+ "oidcMetadataUrl": "https://login.example.com/.well-known/openid-configuration",
33
+ "publicRoutes": ["/health"],
34
+ "cors": {
35
+ "allowedOrigins": [
36
+ "https://myapp.example.com",
37
+ "http://localhost:5173"
38
+ ]
32
39
  }
33
40
  },
34
41
  "modules": {
@@ -60,6 +67,36 @@ metadata, with optional `.env` overrides for secrets and local tweaks.
60
67
  `modules.userPanel.plans.items`. Add plans with `apitogo config` or edit the manifest by hand.
61
68
  See [Gateway plans](./plans.md). Legacy root `plans` is migrated on load.
62
69
  - **Docs / API keys:** configure under `modules.docs` and `modules.userPanel.apiKeys` only.
70
+ - **Gateway:** top-level `gateway` (not under `authentication`). See [Gateway CORS](#gateway-cors)
71
+ and [Gateway plans](./plans.md).
72
+
73
+ ## Gateway CORS
74
+
75
+ Browser calls from your developer portal (and any other SPA) to the API gateway are cross-origin.
76
+ APItoGo always allows the portal site origin. Add extra origins under `gateway.cors.allowedOrigins`
77
+ when tenant apps or local frontends call the gateway:
78
+
79
+ ```json
80
+ {
81
+ "gateway": {
82
+ "cors": {
83
+ "allowedOrigins": [
84
+ "https://myapp.example.com",
85
+ "http://localhost:5173"
86
+ ]
87
+ }
88
+ }
89
+ }
90
+ ```
91
+
92
+ | Rule | Detail |
93
+ | ---- | ------ |
94
+ | Portal origin | Always allowed by the platform (`https://{your-portal-host}`). Do not list it here. |
95
+ | Extra origins | Absolute `http://` or `https://` origins only — no path, query, or fragment. |
96
+ | Wildcard | `"*"` is allowed (replaces the list; credentials stay off). |
97
+ | Empty / omitted | Portal playground still works; only extras are missing. |
98
+
99
+ Apply with `apitogo apply` / `publish_dev_portal` so APIM policies pick up the change.
63
100
 
64
101
  ## Environment variables
65
102
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lukoweb/apitogo",
3
- "version": "0.1.92",
3
+ "version": "0.1.94",
4
4
  "type": "module",
5
5
  "sideEffects": [
6
6
  "**/*.css",
@@ -297,28 +297,17 @@ function firstApiInput(config: ApitogoJsonConfig): string | undefined {
297
297
  return undefined;
298
298
  }
299
299
 
300
- function mergeAuthenticationGateway(
301
- config: ApitogoJsonConfig,
302
- ): Record<string, unknown> | undefined {
300
+ /** Keep gateway at config root; strip any nested authentication.gateway. */
301
+ function normalizeGatewayPlacement(config: ApitogoJsonConfig): void {
303
302
  const authentication = asRecord(config.authentication);
304
303
  if (!authentication) {
305
- return undefined;
304
+ return;
306
305
  }
307
306
 
308
- const authGateway = asRecord(authentication.gateway);
309
- const rootGateway = asRecord(config.gateway);
310
-
311
- if (!authGateway && !rootGateway) {
312
- return authentication;
307
+ if ("gateway" in authentication) {
308
+ delete authentication.gateway;
309
+ config.authentication = authentication;
313
310
  }
314
-
315
- return {
316
- ...authentication,
317
- gateway: {
318
- ...rootGateway,
319
- ...authGateway,
320
- },
321
- };
322
311
  }
323
312
 
324
313
  function ensureModules(config: ApitogoJsonConfig): Record<string, unknown> {
@@ -403,11 +392,10 @@ export function normalizeApitogoConfig(
403
392
  }
404
393
  normalized.site = site;
405
394
 
406
- const authentication = mergeAuthenticationGateway(normalized);
395
+ const authentication = asRecord(normalized.authentication);
407
396
  if (authentication) {
408
- normalized.authentication = authentication;
397
+ normalizeGatewayPlacement(normalized);
409
398
  }
410
- delete normalized.gateway;
411
399
 
412
400
  const modules = ensureModules(normalized);
413
401
  const legacyDocs = asRecord(normalized.docs);
@@ -654,6 +642,9 @@ export function deriveManifestFields(
654
642
  continue;
655
643
  }
656
644
  if (key === "gateway") {
645
+ if (normalized.gateway !== undefined) {
646
+ manifest.gateway = normalized.gateway;
647
+ }
657
648
  continue;
658
649
  }
659
650
  if (key === "pricing") {
@@ -686,11 +677,6 @@ export function deriveManifestFields(
686
677
  }
687
678
  }
688
679
 
689
- const authentication = asRecord(normalized.authentication);
690
- if (authentication?.gateway) {
691
- manifest.gateway = authentication.gateway;
692
- }
693
-
694
680
  return manifest;
695
681
  }
696
682
 
@@ -140,6 +140,12 @@ export const DevPortalLocalManifestSchema = z.object({
140
140
  oidcAudiences: z.array(z.string()).optional(),
141
141
  limitsJson: z.string().optional(),
142
142
  publicHealthActionKey: z.string().optional(),
143
+ publicRoutes: z.array(z.string()).optional(),
144
+ cors: z
145
+ .object({
146
+ allowedOrigins: z.array(z.string()).optional(),
147
+ })
148
+ .optional(),
143
149
  })
144
150
  .optional(),
145
151
  projectId: z.string().optional(),