@lukoweb/apitogo 0.2.42 → 0.2.44
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 +291 -36
- package/dist/declarations/lib/plugins/openapi/playground/result-panel/ResultPanel.d.ts +3 -1
- package/dist/declarations/lib/plugins/openapi/playground/warmUpRetry.d.ts +5 -0
- package/package.json +1 -1
- package/src/config/v2/full-server.ts +192 -0
- package/src/config/v2/manifest.ts +472 -452
- package/src/config/v2/services.ts +59 -20
- package/src/config/v2/wrapper.ts +60 -4
- package/src/lib/plugins/openapi/playground/Playground.tsx +619 -602
- package/src/lib/plugins/openapi/playground/result-panel/ResultPanel.tsx +179 -106
- package/src/lib/plugins/openapi/playground/warmUpRetry.ts +43 -0
- package/src/vite/plugin-modules.ts +190 -188
- package/src/vite/watch-ignore.ts +80 -70
package/dist/cli/cli.js
CHANGED
|
@@ -7322,14 +7322,22 @@ function missingManifestServices(resolved, manifest) {
|
|
|
7322
7322
|
)
|
|
7323
7323
|
);
|
|
7324
7324
|
}
|
|
7325
|
+
var PREVIEW_MANAGED_ORIGIN_URL = "http://localhost:8080";
|
|
7326
|
+
function previewGatewayUrl(resolved) {
|
|
7327
|
+
const gateway = resolved.gateway;
|
|
7328
|
+
if (gateway?.enabled !== true) return void 0;
|
|
7329
|
+
return gateway.origin.mode === "proxy" ? gateway.origin.url : PREVIEW_MANAGED_ORIGIN_URL;
|
|
7330
|
+
}
|
|
7325
7331
|
function stubManifest(resolved) {
|
|
7326
7332
|
const portal = resolved.portal;
|
|
7327
7333
|
const consoleOn = portal?.enabled === true && portal.console.enabled;
|
|
7334
|
+
const gatewayUrl = previewGatewayUrl(resolved);
|
|
7328
7335
|
return parseManifest({
|
|
7329
7336
|
schemaVersion: SUPPORTED_MANIFEST_SCHEMA,
|
|
7330
7337
|
preview: true,
|
|
7331
7338
|
project: { id: resolved.project.id, name: resolved.project.name },
|
|
7332
7339
|
services: {
|
|
7340
|
+
...gatewayUrl ? { gateway: { status: "ready", baseUrl: gatewayUrl } } : {},
|
|
7333
7341
|
...consoleOn && resolved.identity?.enabled ? {
|
|
7334
7342
|
identity: {
|
|
7335
7343
|
provider: "apitogo",
|
|
@@ -7924,6 +7932,17 @@ function planPortal(profile, manifest) {
|
|
|
7924
7932
|
skip("console", "portal.console.enabled is false");
|
|
7925
7933
|
return plan2;
|
|
7926
7934
|
}
|
|
7935
|
+
if (plan2.preview && identity && !identity.issuer) {
|
|
7936
|
+
plan2.demo = true;
|
|
7937
|
+
plan2.userPanel = true;
|
|
7938
|
+
skip("sign-in", "preview: the demo account signs in");
|
|
7939
|
+
if (plans)
|
|
7940
|
+
plan2.plansPlugin = {
|
|
7941
|
+
billing: "none",
|
|
7942
|
+
links: plan2.apiReference ? { api: `/${API_REFERENCE_PATH}` } : {}
|
|
7943
|
+
};
|
|
7944
|
+
return plan2;
|
|
7945
|
+
}
|
|
7927
7946
|
if (!identity) {
|
|
7928
7947
|
skip("sign-in", "services.json has no identity entry");
|
|
7929
7948
|
} else if (!serviceReady(identity)) {
|
|
@@ -8026,22 +8045,26 @@ function buildSiteConfig(resolved, plan2, theme, extras = {}) {
|
|
|
8026
8045
|
]
|
|
8027
8046
|
} : {},
|
|
8028
8047
|
...navigation.length > 0 ? { navigation } : {},
|
|
8029
|
-
// The sidebar link above is only seen inside the docs; the header is what every page shows.
|
|
8048
|
+
// The sidebar link above is only seen inside the docs; the header is what every page shows. A header navigation
|
|
8049
|
+
// replaces the docs tab, so the docs get a header link of their own.
|
|
8030
8050
|
...plan2.apiReference ? {
|
|
8031
8051
|
header: {
|
|
8032
8052
|
navigation: [
|
|
8053
|
+
...plan2.docs && extras.docsPath ? [{ label: "Documentation", to: extras.docsPath }] : [],
|
|
8033
8054
|
{ label: "API Reference", to: `/${API_REFERENCE_PATH}` }
|
|
8034
8055
|
]
|
|
8035
8056
|
}
|
|
8036
8057
|
} : {},
|
|
8058
|
+
...extras.demoPlans?.length ? { plans: extras.demoPlans } : {},
|
|
8037
8059
|
...redirects.length > 0 ? { redirects } : {},
|
|
8038
8060
|
...extras.protectedRoutes?.length ? { protectedRoutes: extras.protectedRoutes } : {},
|
|
8039
8061
|
...theme !== void 0 ? { theme } : {},
|
|
8040
|
-
...plan2.auth ? { authentication: authenticationFor(plan2) } : {},
|
|
8062
|
+
...plan2.auth || plan2.demo ? { authentication: authenticationFor(plan2) } : {},
|
|
8041
8063
|
...plan2.preview ? { preview: true } : {}
|
|
8042
8064
|
};
|
|
8043
8065
|
}
|
|
8044
8066
|
function authenticationFor(plan2) {
|
|
8067
|
+
if (plan2.demo) return { type: "dev-portal", demoMode: true };
|
|
8045
8068
|
if (plan2.plansPlugin)
|
|
8046
8069
|
return {
|
|
8047
8070
|
type: "dev-portal",
|
|
@@ -8053,7 +8076,7 @@ function authenticationFor(plan2) {
|
|
|
8053
8076
|
const { audience: _audience, ...signIn } = plan2.auth ?? {};
|
|
8054
8077
|
return { type: "openid", ...signIn };
|
|
8055
8078
|
}
|
|
8056
|
-
var PREVIEW_BANNER_MESSAGE = "Preview mode: sign
|
|
8079
|
+
var PREVIEW_BANNER_MESSAGE = "Preview mode: sign in with demo@apitogo.local / password to try the console. Run `apitogo deploy` for the real services.";
|
|
8057
8080
|
function renderWrapper(plan2) {
|
|
8058
8081
|
const imports = [
|
|
8059
8082
|
'import { buildApitogoConfig } from "@lukoweb/apitogo/build-config";',
|
|
@@ -8077,6 +8100,15 @@ function renderWrapper(plan2) {
|
|
|
8077
8100
|
""
|
|
8078
8101
|
].join("\n");
|
|
8079
8102
|
}
|
|
8103
|
+
async function readDemoPlans(file) {
|
|
8104
|
+
if (!file) return void 0;
|
|
8105
|
+
try {
|
|
8106
|
+
const parsed = JSON.parse(await readFile6(file, "utf8"));
|
|
8107
|
+
return Array.isArray(parsed.plans) ? parsed.plans : void 0;
|
|
8108
|
+
} catch {
|
|
8109
|
+
return void 0;
|
|
8110
|
+
}
|
|
8111
|
+
}
|
|
8080
8112
|
async function readTheme(resolved) {
|
|
8081
8113
|
const file = resolved.portal?.theme;
|
|
8082
8114
|
if (!file) return void 0;
|
|
@@ -8164,6 +8196,8 @@ async function preparePortal(resolved, manifest, mode, options = {}) {
|
|
|
8164
8196
|
{
|
|
8165
8197
|
redirects: home ? [home] : [],
|
|
8166
8198
|
docsNavigation: docsNavigation(hostedDocRoutes),
|
|
8199
|
+
docsPath: pickLandingRoute(hostedDocRoutes),
|
|
8200
|
+
demoPlans: plan2.demo && plan2.plansPlugin ? await readDemoPlans(resolved.plans?.file) : void 0,
|
|
8167
8201
|
protectedRoutes: protectedRoutesFor({
|
|
8168
8202
|
requireAuth: resolved.portal?.requireAuth,
|
|
8169
8203
|
hasAuth: !!plan2.auth,
|
|
@@ -8337,6 +8371,179 @@ import {
|
|
|
8337
8371
|
} from "node:fs/promises";
|
|
8338
8372
|
import path14 from "node:path";
|
|
8339
8373
|
|
|
8374
|
+
// src/config/v2/full-server.ts
|
|
8375
|
+
var FULL_SERVER_SOURCE = "server";
|
|
8376
|
+
var FULL_SERVER_RUNTIME = "nodejs";
|
|
8377
|
+
var FULL_SERVER_RUNTIME_VERSION = "NODE_22_LTS";
|
|
8378
|
+
var FULL_SERVER_HEALTH_PATH = "/health";
|
|
8379
|
+
var FULL_SERVER_TIMEOUT_SECONDS = 120;
|
|
8380
|
+
var packageName = (name) => `${name.toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "") || "api"}-server`;
|
|
8381
|
+
function fullServerFiles(name) {
|
|
8382
|
+
return {
|
|
8383
|
+
"package.json": `${JSON.stringify(
|
|
8384
|
+
{
|
|
8385
|
+
name: packageName(name),
|
|
8386
|
+
version: "1.0.0",
|
|
8387
|
+
private: true,
|
|
8388
|
+
type: "module",
|
|
8389
|
+
main: "index.js",
|
|
8390
|
+
scripts: { start: "node index.js" },
|
|
8391
|
+
engines: { node: ">=22" }
|
|
8392
|
+
},
|
|
8393
|
+
null,
|
|
8394
|
+
2
|
|
8395
|
+
)}
|
|
8396
|
+
`,
|
|
8397
|
+
"index.js": `// A small API for ${JSON.stringify(name)}. Every endpoint here is listed in apitogo/apis/openapi.yaml:
|
|
8398
|
+
// add an endpoint in both places, then run \`apitogo deploy\`.
|
|
8399
|
+
import { createServer } from "node:http";
|
|
8400
|
+
|
|
8401
|
+
const items = [
|
|
8402
|
+
{ id: "1", name: "First item" },
|
|
8403
|
+
{ id: "2", name: "Second item" },
|
|
8404
|
+
{ id: "3", name: "Third item" },
|
|
8405
|
+
];
|
|
8406
|
+
|
|
8407
|
+
// Lets the portal's API page call this server while you run it locally (\`apitogo dev\`). Once deployed, the gateway
|
|
8408
|
+
// answers browsers itself and keeps its own list of allowed sites.
|
|
8409
|
+
const cors = {
|
|
8410
|
+
"access-control-allow-origin": "*",
|
|
8411
|
+
"access-control-allow-methods": "GET, OPTIONS",
|
|
8412
|
+
"access-control-allow-headers": "*",
|
|
8413
|
+
};
|
|
8414
|
+
|
|
8415
|
+
function send(res, status, body) {
|
|
8416
|
+
res.writeHead(status, { "content-type": "application/json", ...cors });
|
|
8417
|
+
res.end(JSON.stringify(body));
|
|
8418
|
+
}
|
|
8419
|
+
|
|
8420
|
+
const server = createServer((req, res) => {
|
|
8421
|
+
const url = new URL(req.url ?? "/", "http://localhost");
|
|
8422
|
+
const itemMatch = url.pathname.match(/^\\/items\\/([^/]+)$/);
|
|
8423
|
+
|
|
8424
|
+
if (req.method === "OPTIONS") {
|
|
8425
|
+
res.writeHead(204, cors);
|
|
8426
|
+
return res.end();
|
|
8427
|
+
}
|
|
8428
|
+
if (req.method !== "GET") return send(res, 405, { error: "Method not allowed" });
|
|
8429
|
+
if (url.pathname === "/health") return send(res, 200, { status: "ok" });
|
|
8430
|
+
if (url.pathname === "/hello") {
|
|
8431
|
+
const who = url.searchParams.get("name") || "world";
|
|
8432
|
+
return send(res, 200, { message: \`Hello, \${who}!\` });
|
|
8433
|
+
}
|
|
8434
|
+
if (url.pathname === "/items") return send(res, 200, items);
|
|
8435
|
+
if (itemMatch) {
|
|
8436
|
+
const item = items.find((candidate) => candidate.id === itemMatch[1]);
|
|
8437
|
+
return item ? send(res, 200, item) : send(res, 404, { error: "Item not found" });
|
|
8438
|
+
}
|
|
8439
|
+
return send(res, 404, { error: "Not found" });
|
|
8440
|
+
});
|
|
8441
|
+
|
|
8442
|
+
const port = Number(process.env.PORT) || 8080;
|
|
8443
|
+
server.listen(port, () => console.log(\`API listening on port \${port}\`));
|
|
8444
|
+
`,
|
|
8445
|
+
"README.md": `# ${name} server
|
|
8446
|
+
|
|
8447
|
+
A small Node.js API with no dependencies, created by \`apitogo create --template full\`.
|
|
8448
|
+
|
|
8449
|
+
- Run it locally: \`npm start\` (port 8080, or \`PORT\`)
|
|
8450
|
+
- Deploy it: \`apitogo deploy\` from the project folder
|
|
8451
|
+
|
|
8452
|
+
Every endpoint in \`index.js\` is also listed in \`apitogo/apis/openapi.yaml\`. Keep the two in step: the gateway only
|
|
8453
|
+
forwards the endpoints the file names, and the API reference of the portal is built from it.
|
|
8454
|
+
`
|
|
8455
|
+
};
|
|
8456
|
+
}
|
|
8457
|
+
var FULL_SPEC = (name) => `openapi: 3.1.0
|
|
8458
|
+
info:
|
|
8459
|
+
title: ${JSON.stringify(name)}
|
|
8460
|
+
version: 1.0.0
|
|
8461
|
+
description: A small sample API. Edit server/index.js and this file together.
|
|
8462
|
+
paths:
|
|
8463
|
+
/health:
|
|
8464
|
+
get:
|
|
8465
|
+
operationId: getHealth
|
|
8466
|
+
summary: Health check
|
|
8467
|
+
responses:
|
|
8468
|
+
"200":
|
|
8469
|
+
description: The service is up.
|
|
8470
|
+
content:
|
|
8471
|
+
application/json:
|
|
8472
|
+
schema:
|
|
8473
|
+
type: object
|
|
8474
|
+
properties:
|
|
8475
|
+
status:
|
|
8476
|
+
type: string
|
|
8477
|
+
example: ok
|
|
8478
|
+
/hello:
|
|
8479
|
+
get:
|
|
8480
|
+
operationId: getHello
|
|
8481
|
+
summary: Say hello
|
|
8482
|
+
parameters:
|
|
8483
|
+
- name: name
|
|
8484
|
+
in: query
|
|
8485
|
+
required: false
|
|
8486
|
+
description: Who to greet.
|
|
8487
|
+
schema:
|
|
8488
|
+
type: string
|
|
8489
|
+
example: developer
|
|
8490
|
+
responses:
|
|
8491
|
+
"200":
|
|
8492
|
+
description: A greeting.
|
|
8493
|
+
content:
|
|
8494
|
+
application/json:
|
|
8495
|
+
schema:
|
|
8496
|
+
type: object
|
|
8497
|
+
properties:
|
|
8498
|
+
message:
|
|
8499
|
+
type: string
|
|
8500
|
+
example: Hello, developer!
|
|
8501
|
+
/items:
|
|
8502
|
+
get:
|
|
8503
|
+
operationId: listItems
|
|
8504
|
+
summary: List items
|
|
8505
|
+
responses:
|
|
8506
|
+
"200":
|
|
8507
|
+
description: All items.
|
|
8508
|
+
content:
|
|
8509
|
+
application/json:
|
|
8510
|
+
schema:
|
|
8511
|
+
type: array
|
|
8512
|
+
items:
|
|
8513
|
+
$ref: "#/components/schemas/Item"
|
|
8514
|
+
/items/{id}:
|
|
8515
|
+
get:
|
|
8516
|
+
operationId: getItem
|
|
8517
|
+
summary: Get one item
|
|
8518
|
+
parameters:
|
|
8519
|
+
- name: id
|
|
8520
|
+
in: path
|
|
8521
|
+
required: true
|
|
8522
|
+
schema:
|
|
8523
|
+
type: string
|
|
8524
|
+
example: "1"
|
|
8525
|
+
responses:
|
|
8526
|
+
"200":
|
|
8527
|
+
description: The item.
|
|
8528
|
+
content:
|
|
8529
|
+
application/json:
|
|
8530
|
+
schema:
|
|
8531
|
+
$ref: "#/components/schemas/Item"
|
|
8532
|
+
"404":
|
|
8533
|
+
description: No item has this id.
|
|
8534
|
+
components:
|
|
8535
|
+
schemas:
|
|
8536
|
+
Item:
|
|
8537
|
+
type: object
|
|
8538
|
+
properties:
|
|
8539
|
+
id:
|
|
8540
|
+
type: string
|
|
8541
|
+
example: "1"
|
|
8542
|
+
name:
|
|
8543
|
+
type: string
|
|
8544
|
+
example: First item
|
|
8545
|
+
`;
|
|
8546
|
+
|
|
8340
8547
|
// src/config/v2/starter-landing.ts
|
|
8341
8548
|
function starterLandingPage(options) {
|
|
8342
8549
|
const name = JSON.stringify(options.name);
|
|
@@ -8523,6 +8730,7 @@ var serviceLabel = (service) => `${SERVICE_LABELS[service]} (${service})`;
|
|
|
8523
8730
|
function isServiceName(value) {
|
|
8524
8731
|
return SERVICES.includes(value);
|
|
8525
8732
|
}
|
|
8733
|
+
var isFilled = (template) => template === "starter" || template === "full";
|
|
8526
8734
|
var exists3 = (target) => access3(target).then(
|
|
8527
8735
|
() => true,
|
|
8528
8736
|
() => false
|
|
@@ -8688,7 +8896,7 @@ async function buildPortal(context) {
|
|
|
8688
8896
|
const pages = path14.join(resolved.root, "portal", "pages");
|
|
8689
8897
|
if (template === "blank")
|
|
8690
8898
|
await addFile(context, path14.join(pages, ".gitkeep"), "");
|
|
8691
|
-
else if (options.template
|
|
8899
|
+
else if (isFilled(options.template)) {
|
|
8692
8900
|
await addFile(
|
|
8693
8901
|
context,
|
|
8694
8902
|
path14.join(pages, "introduction.mdx"),
|
|
@@ -8727,21 +8935,30 @@ async function buildPortal(context) {
|
|
|
8727
8935
|
...o.search && !external ? { search: o.search } : {}
|
|
8728
8936
|
};
|
|
8729
8937
|
}
|
|
8730
|
-
async function createSourceFolder(context, sourcePath) {
|
|
8938
|
+
async function createSourceFolder(context, sourcePath, runtime) {
|
|
8731
8939
|
const folder = path14.join(context.dir, sourcePath);
|
|
8732
8940
|
const entries = await readdir3(folder).catch(() => []);
|
|
8733
|
-
if (entries.some((name) => name !== ".gitkeep")) return;
|
|
8941
|
+
if (entries.some((name) => name !== ".gitkeep")) return false;
|
|
8942
|
+
if (context.options.template === "full" && runtime === FULL_SERVER_RUNTIME) {
|
|
8943
|
+
for (const [file, content] of Object.entries(
|
|
8944
|
+
fullServerFiles(context.resolved.project.name)
|
|
8945
|
+
))
|
|
8946
|
+
await addFile(context, path14.join(folder, file), content);
|
|
8947
|
+
return true;
|
|
8948
|
+
}
|
|
8734
8949
|
if (entries.length === 0)
|
|
8735
8950
|
await addFile(context, path14.join(folder, ".gitkeep"), "");
|
|
8736
8951
|
context.notes.push(
|
|
8737
8952
|
`The folder ${sourcePath}/ is empty: put your backend code there before you deploy.`
|
|
8738
8953
|
);
|
|
8954
|
+
return false;
|
|
8739
8955
|
}
|
|
8740
8956
|
async function buildGateway(context) {
|
|
8741
8957
|
const { options, dir, resolved } = context;
|
|
8742
8958
|
const o = options.gateway ?? {};
|
|
8743
8959
|
const ask4 = options.ask;
|
|
8744
|
-
|
|
8960
|
+
const full = options.template === "full" && !o.url && o.origin !== "proxy";
|
|
8961
|
+
let mode = o.origin ?? (o.url ? "proxy" : o.source || o.runtime || full ? "managed" : void 0);
|
|
8745
8962
|
if (!mode && ask4) {
|
|
8746
8963
|
mode = await ask4.choose("How does the gateway reach your API?", [
|
|
8747
8964
|
{
|
|
@@ -8761,6 +8978,7 @@ async function buildGateway(context) {
|
|
|
8761
8978
|
);
|
|
8762
8979
|
}
|
|
8763
8980
|
let origin;
|
|
8981
|
+
let sampleServer = false;
|
|
8764
8982
|
if (mode === "proxy") {
|
|
8765
8983
|
const url2 = o.url ?? (ask4 ? await ask4.text("https address of your API", {
|
|
8766
8984
|
validate: httpsProblem
|
|
@@ -8769,15 +8987,15 @@ async function buildGateway(context) {
|
|
|
8769
8987
|
throw new ServiceEditError("--origin proxy needs --url <https url>.");
|
|
8770
8988
|
origin = { mode, url: url2 };
|
|
8771
8989
|
} else {
|
|
8772
|
-
const source = o.source ?? (ask4 ? await ask4.text("Folder with your code, relative to the project", {
|
|
8990
|
+
const source = o.source ?? (full ? FULL_SERVER_SOURCE : void 0) ?? (ask4 ? await ask4.text("Folder with your code, relative to the project", {
|
|
8773
8991
|
default: "server"
|
|
8774
8992
|
}) : void 0);
|
|
8775
|
-
const runtime = o.runtime ?? (ask4 ? await ask4.choose("Runtime of your code", [
|
|
8993
|
+
const runtime = o.runtime ?? (full ? FULL_SERVER_RUNTIME : void 0) ?? (ask4 ? await ask4.choose("Runtime of your code", [
|
|
8776
8994
|
{ value: "nodejs", label: "Node.js" },
|
|
8777
8995
|
{ value: "python", label: "Python" },
|
|
8778
8996
|
{ value: "dotnet", label: ".NET" }
|
|
8779
8997
|
]) : void 0);
|
|
8780
|
-
const runtimeVersion = o.runtimeVersion !== void 0 && runtime ? runtimeVersionSlug(runtime, o.runtimeVersion) : ask4 && runtime ? await ask4.choose(
|
|
8998
|
+
const runtimeVersion = o.runtimeVersion !== void 0 && runtime ? runtimeVersionSlug(runtime, o.runtimeVersion) : full && runtime === FULL_SERVER_RUNTIME ? FULL_SERVER_RUNTIME_VERSION : ask4 && runtime ? await ask4.choose(
|
|
8781
8999
|
"Runtime version",
|
|
8782
9000
|
RUNTIME_VERSIONS[runtime].map((choice, index) => ({
|
|
8783
9001
|
value: choice.value,
|
|
@@ -8791,14 +9009,17 @@ async function buildGateway(context) {
|
|
|
8791
9009
|
);
|
|
8792
9010
|
}
|
|
8793
9011
|
const sourcePath = insideProject(dir, source, "Source folder");
|
|
9012
|
+
sampleServer = await createSourceFolder(context, sourcePath, runtime);
|
|
8794
9013
|
origin = {
|
|
8795
9014
|
mode,
|
|
8796
9015
|
source: sourcePath,
|
|
8797
9016
|
runtime,
|
|
8798
9017
|
runtimeVersion,
|
|
8799
|
-
...o.exclude?.length ? { exclude: o.exclude } : {}
|
|
9018
|
+
...o.exclude?.length ? { exclude: o.exclude } : {},
|
|
9019
|
+
...sampleServer ? { healthPath: FULL_SERVER_HEALTH_PATH } : {},
|
|
9020
|
+
// A full project's API is hosted by the platform and sleeps when idle; the first call must outlast its start.
|
|
9021
|
+
...full ? { timeoutSeconds: FULL_SERVER_TIMEOUT_SECONDS } : {}
|
|
8800
9022
|
};
|
|
8801
|
-
await createSourceFolder(context, sourcePath);
|
|
8802
9023
|
}
|
|
8803
9024
|
let specs;
|
|
8804
9025
|
let requestedSpecs = o.spec ?? [];
|
|
@@ -8833,7 +9054,7 @@ async function buildGateway(context) {
|
|
|
8833
9054
|
await addFile(
|
|
8834
9055
|
context,
|
|
8835
9056
|
target,
|
|
8836
|
-
options.template === "blank" ? BLANK_SPEC(resolved.project.name) : STARTER_SPEC(resolved.project.name)
|
|
9057
|
+
options.template === "blank" ? BLANK_SPEC(resolved.project.name) : sampleServer ? FULL_SPEC(resolved.project.name) : STARTER_SPEC(resolved.project.name)
|
|
8837
9058
|
);
|
|
8838
9059
|
specs = [rel(dir, target)];
|
|
8839
9060
|
} else {
|
|
@@ -8921,7 +9142,7 @@ async function buildIdentity(context) {
|
|
|
8921
9142
|
async function buildPlans(context) {
|
|
8922
9143
|
const { options, dir, resolved } = context;
|
|
8923
9144
|
const o = options.plans ?? {};
|
|
8924
|
-
const template = o.template ?? (options.template === "blank" ? "none" : options.template
|
|
9145
|
+
const template = o.template ?? (options.template === "blank" ? "none" : isFilled(options.template) ? "starter" : "free");
|
|
8925
9146
|
const billing = o.billing ?? (template === "tiered" ? "stripe" : "none");
|
|
8926
9147
|
if (template === "tiered" && billing === "none") {
|
|
8927
9148
|
throw new ServiceEditError(
|
|
@@ -10371,7 +10592,7 @@ import {
|
|
|
10371
10592
|
// package.json
|
|
10372
10593
|
var package_default = {
|
|
10373
10594
|
name: "@lukoweb/apitogo",
|
|
10374
|
-
version: "0.2.
|
|
10595
|
+
version: "0.2.44",
|
|
10375
10596
|
type: "module",
|
|
10376
10597
|
sideEffects: [
|
|
10377
10598
|
"**/*.css",
|
|
@@ -14193,6 +14414,8 @@ var viteModulesPlugin = () => {
|
|
|
14193
14414
|
code.push(
|
|
14194
14415
|
`const userPanel = userPanelModule({`,
|
|
14195
14416
|
` ...${JSON.stringify(userPanelConfig)},`,
|
|
14417
|
+
// With billing on, the billing plugin owns the plans page and the account side menu.
|
|
14418
|
+
` billingNav: ${config2.__meta.pricingEnabled === true},`,
|
|
14196
14419
|
` dashboard: {`,
|
|
14197
14420
|
` ...${JSON.stringify(userPanelConfig.dashboard)},`,
|
|
14198
14421
|
` component: config.modules?.userPanel?.dashboard?.component${dashboardPagePath || useBillingDashboard ? " ?? UserPanelDashboardPage" : ""},`,
|
|
@@ -14426,7 +14649,11 @@ function vitePlugin() {
|
|
|
14426
14649
|
import { lstatSync, realpathSync } from "node:fs";
|
|
14427
14650
|
import path40 from "node:path";
|
|
14428
14651
|
var STATE_INSIDE_CONTENT = /[\\/]content[\\/](?:\.apitogo|apitogo[\\/]\.state)(?:[\\/]|$)/;
|
|
14429
|
-
var within = (base, file) =>
|
|
14652
|
+
var within = (base, file) => {
|
|
14653
|
+
const resolvedBase = path40.resolve(base);
|
|
14654
|
+
const resolvedFile = path40.resolve(file);
|
|
14655
|
+
return resolvedFile === resolvedBase || resolvedFile.startsWith(resolvedBase + path40.sep);
|
|
14656
|
+
};
|
|
14430
14657
|
var unique = (paths) => [...new Set(paths)];
|
|
14431
14658
|
function isWorkspace(root) {
|
|
14432
14659
|
try {
|
|
@@ -16216,7 +16443,7 @@ function addOptionsFromArguments(service, argv) {
|
|
|
16216
16443
|
dir,
|
|
16217
16444
|
disabled: argv.disabled === true,
|
|
16218
16445
|
acceptAllDocs: argv.yes === true,
|
|
16219
|
-
template: argv.template === "blank" || argv.template === "none" ? "blank" : argv.template === "starter"
|
|
16446
|
+
template: argv.template === "blank" || argv.template === "none" ? "blank" : argv.template === "starter" || argv.template === "full" ? argv.template : void 0,
|
|
16220
16447
|
portal: service === "portal" ? {
|
|
16221
16448
|
name: argv.name,
|
|
16222
16449
|
docs: parseDocsFlag(argv.docs),
|
|
@@ -16554,8 +16781,15 @@ var add_default = {
|
|
|
16554
16781
|
describe: "plans: catalog file (default <root>/plans.json)"
|
|
16555
16782
|
}).option("template", {
|
|
16556
16783
|
type: "string",
|
|
16557
|
-
choices: [
|
|
16558
|
-
|
|
16784
|
+
choices: [
|
|
16785
|
+
"blank",
|
|
16786
|
+
"starter",
|
|
16787
|
+
"full",
|
|
16788
|
+
"free",
|
|
16789
|
+
"tiered",
|
|
16790
|
+
"none"
|
|
16791
|
+
],
|
|
16792
|
+
describe: "Content to create: blank (empty shell), starter (filled example) or full (starter plus a Node.js backend in server/). Plans also accept free, tiered and none; none is blank"
|
|
16559
16793
|
}).option("billing", {
|
|
16560
16794
|
type: "string",
|
|
16561
16795
|
choices: ["stripe", "none"],
|
|
@@ -21341,9 +21575,15 @@ import { promisify } from "node:util";
|
|
|
21341
21575
|
var execFileAsync = promisify(execFile);
|
|
21342
21576
|
function nextStepHint(dir) {
|
|
21343
21577
|
const linked = loadConfigV2(dir).resolved.project.id;
|
|
21344
|
-
|
|
21578
|
+
const cd = path70.resolve(dir) === path70.resolve(process.cwd());
|
|
21579
|
+
const steps = linked ? "apitogo deploy" : "apitogo project create, then apitogo deploy";
|
|
21580
|
+
return cd ? `Next: ${steps}` : `Next: cd ${JSON.stringify(path70.relative(process.cwd(), dir))}, then ${steps}`;
|
|
21581
|
+
}
|
|
21582
|
+
function projectFolderName(name) {
|
|
21583
|
+
return name.trim().replace(/[<>:"/\\|?*\s]+/g, "-").replace(/^[-.]+|[-.]+$/g, "") || "apitogo-project";
|
|
21345
21584
|
}
|
|
21346
21585
|
var DONE = "done";
|
|
21586
|
+
var TEMPLATES = ["full", "blank", "starter"];
|
|
21347
21587
|
var STARTER_SERVICES = [
|
|
21348
21588
|
"gateway",
|
|
21349
21589
|
"identity",
|
|
@@ -21395,23 +21635,37 @@ async function createProject(argv, deps = {}) {
|
|
|
21395
21635
|
if (!json) printDiagnosticsToConsole(line2);
|
|
21396
21636
|
};
|
|
21397
21637
|
try {
|
|
21398
|
-
const
|
|
21638
|
+
const base = path70.resolve(process.cwd(), argv.dir ?? ".");
|
|
21639
|
+
let dir = base;
|
|
21399
21640
|
let requested = parseServicesFlag(argv.services);
|
|
21400
21641
|
let template = argv.template;
|
|
21401
|
-
if (
|
|
21642
|
+
if (template !== void 0 && !TEMPLATES.includes(template))
|
|
21643
|
+
throw new InitError(
|
|
21644
|
+
`Unknown template: ${template}.`,
|
|
21645
|
+
"Use --template full or --template blank."
|
|
21646
|
+
);
|
|
21647
|
+
const templateServices = template === "starter" || template === "full";
|
|
21648
|
+
if (!asker && requested.length === 0 && !templateServices) {
|
|
21402
21649
|
throw new InitError(
|
|
21403
21650
|
"create needs a terminal to ask its questions, and none is available.",
|
|
21404
|
-
"Pass --services <portal,gateway,identity,plans> (and --template blank|starter), or run `apitogo init` and `apitogo add <service>`."
|
|
21651
|
+
"Pass --template full, or --services <portal,gateway,identity,plans> (and --template blank|starter|full), or run `apitogo init` and `apitogo add <service>`."
|
|
21405
21652
|
);
|
|
21406
21653
|
}
|
|
21654
|
+
const existing = await isConfigV2(base);
|
|
21655
|
+
let name;
|
|
21656
|
+
if (!existing) {
|
|
21657
|
+
name = argv.name?.trim() || (asker ? await asker.text("Project name", {
|
|
21658
|
+
default: await defaultName(base)
|
|
21659
|
+
}) : await defaultName(base));
|
|
21660
|
+
const folder = projectFolderName(name);
|
|
21661
|
+
if (argv.dir === void 0 && folder !== path70.basename(base))
|
|
21662
|
+
dir = path70.join(base, folder);
|
|
21663
|
+
}
|
|
21407
21664
|
if (await (deps.hasUncommittedChanges ?? gitHasUncommittedChanges)(dir))
|
|
21408
21665
|
say(
|
|
21409
21666
|
"This folder has uncommitted changes. Commit first so you can undo this easily."
|
|
21410
21667
|
);
|
|
21411
|
-
if (!await isConfigV2(dir)) {
|
|
21412
|
-
const name = argv.name?.trim() || (asker ? await asker.text("Project name", {
|
|
21413
|
-
default: await defaultName(dir)
|
|
21414
|
-
}) : await defaultName(dir));
|
|
21668
|
+
if (name !== void 0 && !await isConfigV2(dir)) {
|
|
21415
21669
|
const plan2 = await planInit({ dir, name });
|
|
21416
21670
|
for (const line2 of formatInitPlan(plan2)) say(line2);
|
|
21417
21671
|
commands.push(`apitogo init --name ${JSON.stringify(name)}`);
|
|
@@ -21431,7 +21685,9 @@ async function createProject(argv, deps = {}) {
|
|
|
21431
21685
|
return;
|
|
21432
21686
|
}
|
|
21433
21687
|
await applyInitPlan(plan2);
|
|
21434
|
-
say(
|
|
21688
|
+
say(
|
|
21689
|
+
dir === base ? `Created ${APITOGO_CONFIG_FILENAME}.` : `Created ${path70.relative(base, dir)}${path70.sep}${APITOGO_CONFIG_FILENAME}.`
|
|
21690
|
+
);
|
|
21435
21691
|
} else {
|
|
21436
21692
|
say(`${APITOGO_CONFIG_FILENAME} exists: continuing from it.`);
|
|
21437
21693
|
}
|
|
@@ -21440,17 +21696,17 @@ async function createProject(argv, deps = {}) {
|
|
|
21440
21696
|
"What do you want to start from?",
|
|
21441
21697
|
[
|
|
21442
21698
|
{
|
|
21443
|
-
value: "
|
|
21444
|
-
label: "
|
|
21699
|
+
value: "full",
|
|
21700
|
+
label: "Full: every service with examples and a small Node.js backend, ready to deploy"
|
|
21445
21701
|
},
|
|
21446
21702
|
{ value: "blank", label: "Blank: choose the services one by one" }
|
|
21447
21703
|
]
|
|
21448
21704
|
);
|
|
21449
|
-
if (template === "starter" && requested.length === 0)
|
|
21705
|
+
if ((template === "starter" || template === "full") && requested.length === 0)
|
|
21450
21706
|
requested = STARTER_SERVICES;
|
|
21451
21707
|
const stepArgs = (service) => ({
|
|
21452
21708
|
service,
|
|
21453
|
-
dir
|
|
21709
|
+
dir,
|
|
21454
21710
|
template,
|
|
21455
21711
|
dryRun: argv.dryRun,
|
|
21456
21712
|
install: argv.install,
|
|
@@ -21531,8 +21787,7 @@ var create_default = {
|
|
|
21531
21787
|
command: "create",
|
|
21532
21788
|
builder: (yargs2) => yargs2.option("dir", {
|
|
21533
21789
|
type: "string",
|
|
21534
|
-
describe: "Project directory",
|
|
21535
|
-
default: ".",
|
|
21790
|
+
describe: "Project directory (default: a new folder named after the project)",
|
|
21536
21791
|
normalize: true,
|
|
21537
21792
|
hidden: true
|
|
21538
21793
|
}).option("name", {
|
|
@@ -21543,8 +21798,8 @@ var create_default = {
|
|
|
21543
21798
|
describe: "Skip the menu and add these services, comma separated (portal, gateway, identity, plans)"
|
|
21544
21799
|
}).option("template", {
|
|
21545
21800
|
type: "string",
|
|
21546
|
-
|
|
21547
|
-
describe: "Template
|
|
21801
|
+
// `starter` is still accepted for older scripts but no longer offered: `full` replaces it.
|
|
21802
|
+
describe: "Template: full (every service, a sample Node.js backend, ready to deploy) or blank. Default: ask"
|
|
21548
21803
|
}).option("input", {
|
|
21549
21804
|
type: "boolean",
|
|
21550
21805
|
default: true,
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import type { UseMutationResult } from "@tanstack/react-query";
|
|
2
2
|
import type { PlaygroundResult } from "../Playground.js";
|
|
3
|
-
export declare const ResultPanel: ({ queryMutation, showLongRunningWarning, onCancel, tip, isFinished, progress, }: {
|
|
3
|
+
export declare const ResultPanel: ({ queryMutation, showLongRunningWarning, isWarmingUp, hasIdentities, onCancel, tip, isFinished, progress, }: {
|
|
4
4
|
queryMutation: UseMutationResult<PlaygroundResult, Error, any>;
|
|
5
5
|
showLongRunningWarning?: boolean;
|
|
6
|
+
isWarmingUp?: boolean;
|
|
7
|
+
hasIdentities?: boolean;
|
|
6
8
|
onCancel?: () => void;
|
|
7
9
|
isFinished: boolean;
|
|
8
10
|
progress: number;
|