@lukoweb/apitogo 0.2.42 → 0.2.43
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 +286 -36
- package/package.json +1 -1
- package/src/config/v2/full-server.ts +187 -0
- package/src/config/v2/manifest.ts +472 -452
- package/src/config/v2/services.ts +56 -20
- package/src/config/v2/wrapper.ts +60 -4
- 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,178 @@ 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 packageName = (name) => `${name.toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "") || "api"}-server`;
|
|
8380
|
+
function fullServerFiles(name) {
|
|
8381
|
+
return {
|
|
8382
|
+
"package.json": `${JSON.stringify(
|
|
8383
|
+
{
|
|
8384
|
+
name: packageName(name),
|
|
8385
|
+
version: "1.0.0",
|
|
8386
|
+
private: true,
|
|
8387
|
+
type: "module",
|
|
8388
|
+
main: "index.js",
|
|
8389
|
+
scripts: { start: "node index.js" },
|
|
8390
|
+
engines: { node: ">=22" }
|
|
8391
|
+
},
|
|
8392
|
+
null,
|
|
8393
|
+
2
|
|
8394
|
+
)}
|
|
8395
|
+
`,
|
|
8396
|
+
"index.js": `// A small API for ${JSON.stringify(name)}. Every endpoint here is listed in apitogo/apis/openapi.yaml:
|
|
8397
|
+
// add an endpoint in both places, then run \`apitogo deploy\`.
|
|
8398
|
+
import { createServer } from "node:http";
|
|
8399
|
+
|
|
8400
|
+
const items = [
|
|
8401
|
+
{ id: "1", name: "First item" },
|
|
8402
|
+
{ id: "2", name: "Second item" },
|
|
8403
|
+
{ id: "3", name: "Third item" },
|
|
8404
|
+
];
|
|
8405
|
+
|
|
8406
|
+
// Lets the portal's API page call this server while you run it locally (\`apitogo dev\`). Once deployed, the gateway
|
|
8407
|
+
// answers browsers itself and keeps its own list of allowed sites.
|
|
8408
|
+
const cors = {
|
|
8409
|
+
"access-control-allow-origin": "*",
|
|
8410
|
+
"access-control-allow-methods": "GET, OPTIONS",
|
|
8411
|
+
"access-control-allow-headers": "*",
|
|
8412
|
+
};
|
|
8413
|
+
|
|
8414
|
+
function send(res, status, body) {
|
|
8415
|
+
res.writeHead(status, { "content-type": "application/json", ...cors });
|
|
8416
|
+
res.end(JSON.stringify(body));
|
|
8417
|
+
}
|
|
8418
|
+
|
|
8419
|
+
const server = createServer((req, res) => {
|
|
8420
|
+
const url = new URL(req.url ?? "/", "http://localhost");
|
|
8421
|
+
const itemMatch = url.pathname.match(/^\\/items\\/([^/]+)$/);
|
|
8422
|
+
|
|
8423
|
+
if (req.method === "OPTIONS") {
|
|
8424
|
+
res.writeHead(204, cors);
|
|
8425
|
+
return res.end();
|
|
8426
|
+
}
|
|
8427
|
+
if (req.method !== "GET") return send(res, 405, { error: "Method not allowed" });
|
|
8428
|
+
if (url.pathname === "/health") return send(res, 200, { status: "ok" });
|
|
8429
|
+
if (url.pathname === "/hello") {
|
|
8430
|
+
const who = url.searchParams.get("name") || "world";
|
|
8431
|
+
return send(res, 200, { message: \`Hello, \${who}!\` });
|
|
8432
|
+
}
|
|
8433
|
+
if (url.pathname === "/items") return send(res, 200, items);
|
|
8434
|
+
if (itemMatch) {
|
|
8435
|
+
const item = items.find((candidate) => candidate.id === itemMatch[1]);
|
|
8436
|
+
return item ? send(res, 200, item) : send(res, 404, { error: "Item not found" });
|
|
8437
|
+
}
|
|
8438
|
+
return send(res, 404, { error: "Not found" });
|
|
8439
|
+
});
|
|
8440
|
+
|
|
8441
|
+
const port = Number(process.env.PORT) || 8080;
|
|
8442
|
+
server.listen(port, () => console.log(\`API listening on port \${port}\`));
|
|
8443
|
+
`,
|
|
8444
|
+
"README.md": `# ${name} server
|
|
8445
|
+
|
|
8446
|
+
A small Node.js API with no dependencies, created by \`apitogo create --template full\`.
|
|
8447
|
+
|
|
8448
|
+
- Run it locally: \`npm start\` (port 8080, or \`PORT\`)
|
|
8449
|
+
- Deploy it: \`apitogo deploy\` from the project folder
|
|
8450
|
+
|
|
8451
|
+
Every endpoint in \`index.js\` is also listed in \`apitogo/apis/openapi.yaml\`. Keep the two in step: the gateway only
|
|
8452
|
+
forwards the endpoints the file names, and the API reference of the portal is built from it.
|
|
8453
|
+
`
|
|
8454
|
+
};
|
|
8455
|
+
}
|
|
8456
|
+
var FULL_SPEC = (name) => `openapi: 3.1.0
|
|
8457
|
+
info:
|
|
8458
|
+
title: ${JSON.stringify(name)}
|
|
8459
|
+
version: 1.0.0
|
|
8460
|
+
description: A small sample API. Edit server/index.js and this file together.
|
|
8461
|
+
paths:
|
|
8462
|
+
/health:
|
|
8463
|
+
get:
|
|
8464
|
+
operationId: getHealth
|
|
8465
|
+
summary: Health check
|
|
8466
|
+
responses:
|
|
8467
|
+
"200":
|
|
8468
|
+
description: The service is up.
|
|
8469
|
+
content:
|
|
8470
|
+
application/json:
|
|
8471
|
+
schema:
|
|
8472
|
+
type: object
|
|
8473
|
+
properties:
|
|
8474
|
+
status:
|
|
8475
|
+
type: string
|
|
8476
|
+
example: ok
|
|
8477
|
+
/hello:
|
|
8478
|
+
get:
|
|
8479
|
+
operationId: getHello
|
|
8480
|
+
summary: Say hello
|
|
8481
|
+
parameters:
|
|
8482
|
+
- name: name
|
|
8483
|
+
in: query
|
|
8484
|
+
required: false
|
|
8485
|
+
description: Who to greet.
|
|
8486
|
+
schema:
|
|
8487
|
+
type: string
|
|
8488
|
+
example: developer
|
|
8489
|
+
responses:
|
|
8490
|
+
"200":
|
|
8491
|
+
description: A greeting.
|
|
8492
|
+
content:
|
|
8493
|
+
application/json:
|
|
8494
|
+
schema:
|
|
8495
|
+
type: object
|
|
8496
|
+
properties:
|
|
8497
|
+
message:
|
|
8498
|
+
type: string
|
|
8499
|
+
example: Hello, developer!
|
|
8500
|
+
/items:
|
|
8501
|
+
get:
|
|
8502
|
+
operationId: listItems
|
|
8503
|
+
summary: List items
|
|
8504
|
+
responses:
|
|
8505
|
+
"200":
|
|
8506
|
+
description: All items.
|
|
8507
|
+
content:
|
|
8508
|
+
application/json:
|
|
8509
|
+
schema:
|
|
8510
|
+
type: array
|
|
8511
|
+
items:
|
|
8512
|
+
$ref: "#/components/schemas/Item"
|
|
8513
|
+
/items/{id}:
|
|
8514
|
+
get:
|
|
8515
|
+
operationId: getItem
|
|
8516
|
+
summary: Get one item
|
|
8517
|
+
parameters:
|
|
8518
|
+
- name: id
|
|
8519
|
+
in: path
|
|
8520
|
+
required: true
|
|
8521
|
+
schema:
|
|
8522
|
+
type: string
|
|
8523
|
+
example: "1"
|
|
8524
|
+
responses:
|
|
8525
|
+
"200":
|
|
8526
|
+
description: The item.
|
|
8527
|
+
content:
|
|
8528
|
+
application/json:
|
|
8529
|
+
schema:
|
|
8530
|
+
$ref: "#/components/schemas/Item"
|
|
8531
|
+
"404":
|
|
8532
|
+
description: No item has this id.
|
|
8533
|
+
components:
|
|
8534
|
+
schemas:
|
|
8535
|
+
Item:
|
|
8536
|
+
type: object
|
|
8537
|
+
properties:
|
|
8538
|
+
id:
|
|
8539
|
+
type: string
|
|
8540
|
+
example: "1"
|
|
8541
|
+
name:
|
|
8542
|
+
type: string
|
|
8543
|
+
example: First item
|
|
8544
|
+
`;
|
|
8545
|
+
|
|
8340
8546
|
// src/config/v2/starter-landing.ts
|
|
8341
8547
|
function starterLandingPage(options) {
|
|
8342
8548
|
const name = JSON.stringify(options.name);
|
|
@@ -8523,6 +8729,7 @@ var serviceLabel = (service) => `${SERVICE_LABELS[service]} (${service})`;
|
|
|
8523
8729
|
function isServiceName(value) {
|
|
8524
8730
|
return SERVICES.includes(value);
|
|
8525
8731
|
}
|
|
8732
|
+
var isFilled = (template) => template === "starter" || template === "full";
|
|
8526
8733
|
var exists3 = (target) => access3(target).then(
|
|
8527
8734
|
() => true,
|
|
8528
8735
|
() => false
|
|
@@ -8688,7 +8895,7 @@ async function buildPortal(context) {
|
|
|
8688
8895
|
const pages = path14.join(resolved.root, "portal", "pages");
|
|
8689
8896
|
if (template === "blank")
|
|
8690
8897
|
await addFile(context, path14.join(pages, ".gitkeep"), "");
|
|
8691
|
-
else if (options.template
|
|
8898
|
+
else if (isFilled(options.template)) {
|
|
8692
8899
|
await addFile(
|
|
8693
8900
|
context,
|
|
8694
8901
|
path14.join(pages, "introduction.mdx"),
|
|
@@ -8727,21 +8934,30 @@ async function buildPortal(context) {
|
|
|
8727
8934
|
...o.search && !external ? { search: o.search } : {}
|
|
8728
8935
|
};
|
|
8729
8936
|
}
|
|
8730
|
-
async function createSourceFolder(context, sourcePath) {
|
|
8937
|
+
async function createSourceFolder(context, sourcePath, runtime) {
|
|
8731
8938
|
const folder = path14.join(context.dir, sourcePath);
|
|
8732
8939
|
const entries = await readdir3(folder).catch(() => []);
|
|
8733
|
-
if (entries.some((name) => name !== ".gitkeep")) return;
|
|
8940
|
+
if (entries.some((name) => name !== ".gitkeep")) return false;
|
|
8941
|
+
if (context.options.template === "full" && runtime === FULL_SERVER_RUNTIME) {
|
|
8942
|
+
for (const [file, content] of Object.entries(
|
|
8943
|
+
fullServerFiles(context.resolved.project.name)
|
|
8944
|
+
))
|
|
8945
|
+
await addFile(context, path14.join(folder, file), content);
|
|
8946
|
+
return true;
|
|
8947
|
+
}
|
|
8734
8948
|
if (entries.length === 0)
|
|
8735
8949
|
await addFile(context, path14.join(folder, ".gitkeep"), "");
|
|
8736
8950
|
context.notes.push(
|
|
8737
8951
|
`The folder ${sourcePath}/ is empty: put your backend code there before you deploy.`
|
|
8738
8952
|
);
|
|
8953
|
+
return false;
|
|
8739
8954
|
}
|
|
8740
8955
|
async function buildGateway(context) {
|
|
8741
8956
|
const { options, dir, resolved } = context;
|
|
8742
8957
|
const o = options.gateway ?? {};
|
|
8743
8958
|
const ask4 = options.ask;
|
|
8744
|
-
|
|
8959
|
+
const full = options.template === "full" && !o.url && o.origin !== "proxy";
|
|
8960
|
+
let mode = o.origin ?? (o.url ? "proxy" : o.source || o.runtime || full ? "managed" : void 0);
|
|
8745
8961
|
if (!mode && ask4) {
|
|
8746
8962
|
mode = await ask4.choose("How does the gateway reach your API?", [
|
|
8747
8963
|
{
|
|
@@ -8761,6 +8977,7 @@ async function buildGateway(context) {
|
|
|
8761
8977
|
);
|
|
8762
8978
|
}
|
|
8763
8979
|
let origin;
|
|
8980
|
+
let sampleServer = false;
|
|
8764
8981
|
if (mode === "proxy") {
|
|
8765
8982
|
const url2 = o.url ?? (ask4 ? await ask4.text("https address of your API", {
|
|
8766
8983
|
validate: httpsProblem
|
|
@@ -8769,15 +8986,15 @@ async function buildGateway(context) {
|
|
|
8769
8986
|
throw new ServiceEditError("--origin proxy needs --url <https url>.");
|
|
8770
8987
|
origin = { mode, url: url2 };
|
|
8771
8988
|
} else {
|
|
8772
|
-
const source = o.source ?? (ask4 ? await ask4.text("Folder with your code, relative to the project", {
|
|
8989
|
+
const source = o.source ?? (full ? FULL_SERVER_SOURCE : void 0) ?? (ask4 ? await ask4.text("Folder with your code, relative to the project", {
|
|
8773
8990
|
default: "server"
|
|
8774
8991
|
}) : void 0);
|
|
8775
|
-
const runtime = o.runtime ?? (ask4 ? await ask4.choose("Runtime of your code", [
|
|
8992
|
+
const runtime = o.runtime ?? (full ? FULL_SERVER_RUNTIME : void 0) ?? (ask4 ? await ask4.choose("Runtime of your code", [
|
|
8776
8993
|
{ value: "nodejs", label: "Node.js" },
|
|
8777
8994
|
{ value: "python", label: "Python" },
|
|
8778
8995
|
{ value: "dotnet", label: ".NET" }
|
|
8779
8996
|
]) : void 0);
|
|
8780
|
-
const runtimeVersion = o.runtimeVersion !== void 0 && runtime ? runtimeVersionSlug(runtime, o.runtimeVersion) : ask4 && runtime ? await ask4.choose(
|
|
8997
|
+
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
8998
|
"Runtime version",
|
|
8782
8999
|
RUNTIME_VERSIONS[runtime].map((choice, index) => ({
|
|
8783
9000
|
value: choice.value,
|
|
@@ -8791,14 +9008,15 @@ async function buildGateway(context) {
|
|
|
8791
9008
|
);
|
|
8792
9009
|
}
|
|
8793
9010
|
const sourcePath = insideProject(dir, source, "Source folder");
|
|
9011
|
+
sampleServer = await createSourceFolder(context, sourcePath, runtime);
|
|
8794
9012
|
origin = {
|
|
8795
9013
|
mode,
|
|
8796
9014
|
source: sourcePath,
|
|
8797
9015
|
runtime,
|
|
8798
9016
|
runtimeVersion,
|
|
8799
|
-
...o.exclude?.length ? { exclude: o.exclude } : {}
|
|
9017
|
+
...o.exclude?.length ? { exclude: o.exclude } : {},
|
|
9018
|
+
...sampleServer ? { healthPath: FULL_SERVER_HEALTH_PATH } : {}
|
|
8800
9019
|
};
|
|
8801
|
-
await createSourceFolder(context, sourcePath);
|
|
8802
9020
|
}
|
|
8803
9021
|
let specs;
|
|
8804
9022
|
let requestedSpecs = o.spec ?? [];
|
|
@@ -8833,7 +9051,7 @@ async function buildGateway(context) {
|
|
|
8833
9051
|
await addFile(
|
|
8834
9052
|
context,
|
|
8835
9053
|
target,
|
|
8836
|
-
options.template === "blank" ? BLANK_SPEC(resolved.project.name) : STARTER_SPEC(resolved.project.name)
|
|
9054
|
+
options.template === "blank" ? BLANK_SPEC(resolved.project.name) : sampleServer ? FULL_SPEC(resolved.project.name) : STARTER_SPEC(resolved.project.name)
|
|
8837
9055
|
);
|
|
8838
9056
|
specs = [rel(dir, target)];
|
|
8839
9057
|
} else {
|
|
@@ -8921,7 +9139,7 @@ async function buildIdentity(context) {
|
|
|
8921
9139
|
async function buildPlans(context) {
|
|
8922
9140
|
const { options, dir, resolved } = context;
|
|
8923
9141
|
const o = options.plans ?? {};
|
|
8924
|
-
const template = o.template ?? (options.template === "blank" ? "none" : options.template
|
|
9142
|
+
const template = o.template ?? (options.template === "blank" ? "none" : isFilled(options.template) ? "starter" : "free");
|
|
8925
9143
|
const billing = o.billing ?? (template === "tiered" ? "stripe" : "none");
|
|
8926
9144
|
if (template === "tiered" && billing === "none") {
|
|
8927
9145
|
throw new ServiceEditError(
|
|
@@ -10371,7 +10589,7 @@ import {
|
|
|
10371
10589
|
// package.json
|
|
10372
10590
|
var package_default = {
|
|
10373
10591
|
name: "@lukoweb/apitogo",
|
|
10374
|
-
version: "0.2.
|
|
10592
|
+
version: "0.2.43",
|
|
10375
10593
|
type: "module",
|
|
10376
10594
|
sideEffects: [
|
|
10377
10595
|
"**/*.css",
|
|
@@ -14426,7 +14644,11 @@ function vitePlugin() {
|
|
|
14426
14644
|
import { lstatSync, realpathSync } from "node:fs";
|
|
14427
14645
|
import path40 from "node:path";
|
|
14428
14646
|
var STATE_INSIDE_CONTENT = /[\\/]content[\\/](?:\.apitogo|apitogo[\\/]\.state)(?:[\\/]|$)/;
|
|
14429
|
-
var within = (base, file) =>
|
|
14647
|
+
var within = (base, file) => {
|
|
14648
|
+
const resolvedBase = path40.resolve(base);
|
|
14649
|
+
const resolvedFile = path40.resolve(file);
|
|
14650
|
+
return resolvedFile === resolvedBase || resolvedFile.startsWith(resolvedBase + path40.sep);
|
|
14651
|
+
};
|
|
14430
14652
|
var unique = (paths) => [...new Set(paths)];
|
|
14431
14653
|
function isWorkspace(root) {
|
|
14432
14654
|
try {
|
|
@@ -16216,7 +16438,7 @@ function addOptionsFromArguments(service, argv) {
|
|
|
16216
16438
|
dir,
|
|
16217
16439
|
disabled: argv.disabled === true,
|
|
16218
16440
|
acceptAllDocs: argv.yes === true,
|
|
16219
|
-
template: argv.template === "blank" || argv.template === "none" ? "blank" : argv.template === "starter"
|
|
16441
|
+
template: argv.template === "blank" || argv.template === "none" ? "blank" : argv.template === "starter" || argv.template === "full" ? argv.template : void 0,
|
|
16220
16442
|
portal: service === "portal" ? {
|
|
16221
16443
|
name: argv.name,
|
|
16222
16444
|
docs: parseDocsFlag(argv.docs),
|
|
@@ -16554,8 +16776,15 @@ var add_default = {
|
|
|
16554
16776
|
describe: "plans: catalog file (default <root>/plans.json)"
|
|
16555
16777
|
}).option("template", {
|
|
16556
16778
|
type: "string",
|
|
16557
|
-
choices: [
|
|
16558
|
-
|
|
16779
|
+
choices: [
|
|
16780
|
+
"blank",
|
|
16781
|
+
"starter",
|
|
16782
|
+
"full",
|
|
16783
|
+
"free",
|
|
16784
|
+
"tiered",
|
|
16785
|
+
"none"
|
|
16786
|
+
],
|
|
16787
|
+
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
16788
|
}).option("billing", {
|
|
16560
16789
|
type: "string",
|
|
16561
16790
|
choices: ["stripe", "none"],
|
|
@@ -21341,9 +21570,15 @@ import { promisify } from "node:util";
|
|
|
21341
21570
|
var execFileAsync = promisify(execFile);
|
|
21342
21571
|
function nextStepHint(dir) {
|
|
21343
21572
|
const linked = loadConfigV2(dir).resolved.project.id;
|
|
21344
|
-
|
|
21573
|
+
const cd = path70.resolve(dir) === path70.resolve(process.cwd());
|
|
21574
|
+
const steps = linked ? "apitogo deploy" : "apitogo project create, then apitogo deploy";
|
|
21575
|
+
return cd ? `Next: ${steps}` : `Next: cd ${JSON.stringify(path70.relative(process.cwd(), dir))}, then ${steps}`;
|
|
21576
|
+
}
|
|
21577
|
+
function projectFolderName(name) {
|
|
21578
|
+
return name.trim().replace(/[<>:"/\\|?*\s]+/g, "-").replace(/^[-.]+|[-.]+$/g, "") || "apitogo-project";
|
|
21345
21579
|
}
|
|
21346
21580
|
var DONE = "done";
|
|
21581
|
+
var TEMPLATES = ["full", "blank", "starter"];
|
|
21347
21582
|
var STARTER_SERVICES = [
|
|
21348
21583
|
"gateway",
|
|
21349
21584
|
"identity",
|
|
@@ -21395,23 +21630,37 @@ async function createProject(argv, deps = {}) {
|
|
|
21395
21630
|
if (!json) printDiagnosticsToConsole(line2);
|
|
21396
21631
|
};
|
|
21397
21632
|
try {
|
|
21398
|
-
const
|
|
21633
|
+
const base = path70.resolve(process.cwd(), argv.dir ?? ".");
|
|
21634
|
+
let dir = base;
|
|
21399
21635
|
let requested = parseServicesFlag(argv.services);
|
|
21400
21636
|
let template = argv.template;
|
|
21401
|
-
if (
|
|
21637
|
+
if (template !== void 0 && !TEMPLATES.includes(template))
|
|
21638
|
+
throw new InitError(
|
|
21639
|
+
`Unknown template: ${template}.`,
|
|
21640
|
+
"Use --template full or --template blank."
|
|
21641
|
+
);
|
|
21642
|
+
const templateServices = template === "starter" || template === "full";
|
|
21643
|
+
if (!asker && requested.length === 0 && !templateServices) {
|
|
21402
21644
|
throw new InitError(
|
|
21403
21645
|
"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>`."
|
|
21646
|
+
"Pass --template full, or --services <portal,gateway,identity,plans> (and --template blank|starter|full), or run `apitogo init` and `apitogo add <service>`."
|
|
21405
21647
|
);
|
|
21406
21648
|
}
|
|
21649
|
+
const existing = await isConfigV2(base);
|
|
21650
|
+
let name;
|
|
21651
|
+
if (!existing) {
|
|
21652
|
+
name = argv.name?.trim() || (asker ? await asker.text("Project name", {
|
|
21653
|
+
default: await defaultName(base)
|
|
21654
|
+
}) : await defaultName(base));
|
|
21655
|
+
const folder = projectFolderName(name);
|
|
21656
|
+
if (argv.dir === void 0 && folder !== path70.basename(base))
|
|
21657
|
+
dir = path70.join(base, folder);
|
|
21658
|
+
}
|
|
21407
21659
|
if (await (deps.hasUncommittedChanges ?? gitHasUncommittedChanges)(dir))
|
|
21408
21660
|
say(
|
|
21409
21661
|
"This folder has uncommitted changes. Commit first so you can undo this easily."
|
|
21410
21662
|
);
|
|
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));
|
|
21663
|
+
if (name !== void 0 && !await isConfigV2(dir)) {
|
|
21415
21664
|
const plan2 = await planInit({ dir, name });
|
|
21416
21665
|
for (const line2 of formatInitPlan(plan2)) say(line2);
|
|
21417
21666
|
commands.push(`apitogo init --name ${JSON.stringify(name)}`);
|
|
@@ -21431,7 +21680,9 @@ async function createProject(argv, deps = {}) {
|
|
|
21431
21680
|
return;
|
|
21432
21681
|
}
|
|
21433
21682
|
await applyInitPlan(plan2);
|
|
21434
|
-
say(
|
|
21683
|
+
say(
|
|
21684
|
+
dir === base ? `Created ${APITOGO_CONFIG_FILENAME}.` : `Created ${path70.relative(base, dir)}${path70.sep}${APITOGO_CONFIG_FILENAME}.`
|
|
21685
|
+
);
|
|
21435
21686
|
} else {
|
|
21436
21687
|
say(`${APITOGO_CONFIG_FILENAME} exists: continuing from it.`);
|
|
21437
21688
|
}
|
|
@@ -21440,17 +21691,17 @@ async function createProject(argv, deps = {}) {
|
|
|
21440
21691
|
"What do you want to start from?",
|
|
21441
21692
|
[
|
|
21442
21693
|
{
|
|
21443
|
-
value: "
|
|
21444
|
-
label: "
|
|
21694
|
+
value: "full",
|
|
21695
|
+
label: "Full: every service with examples and a small Node.js backend, ready to deploy"
|
|
21445
21696
|
},
|
|
21446
21697
|
{ value: "blank", label: "Blank: choose the services one by one" }
|
|
21447
21698
|
]
|
|
21448
21699
|
);
|
|
21449
|
-
if (template === "starter" && requested.length === 0)
|
|
21700
|
+
if ((template === "starter" || template === "full") && requested.length === 0)
|
|
21450
21701
|
requested = STARTER_SERVICES;
|
|
21451
21702
|
const stepArgs = (service) => ({
|
|
21452
21703
|
service,
|
|
21453
|
-
dir
|
|
21704
|
+
dir,
|
|
21454
21705
|
template,
|
|
21455
21706
|
dryRun: argv.dryRun,
|
|
21456
21707
|
install: argv.install,
|
|
@@ -21531,8 +21782,7 @@ var create_default = {
|
|
|
21531
21782
|
command: "create",
|
|
21532
21783
|
builder: (yargs2) => yargs2.option("dir", {
|
|
21533
21784
|
type: "string",
|
|
21534
|
-
describe: "Project directory",
|
|
21535
|
-
default: ".",
|
|
21785
|
+
describe: "Project directory (default: a new folder named after the project)",
|
|
21536
21786
|
normalize: true,
|
|
21537
21787
|
hidden: true
|
|
21538
21788
|
}).option("name", {
|
|
@@ -21543,8 +21793,8 @@ var create_default = {
|
|
|
21543
21793
|
describe: "Skip the menu and add these services, comma separated (portal, gateway, identity, plans)"
|
|
21544
21794
|
}).option("template", {
|
|
21545
21795
|
type: "string",
|
|
21546
|
-
|
|
21547
|
-
describe: "Template
|
|
21796
|
+
// `starter` is still accepted for older scripts but no longer offered: `full` replaces it.
|
|
21797
|
+
describe: "Template: full (every service, a sample Node.js backend, ready to deploy) or blank. Default: ask"
|
|
21548
21798
|
}).option("input", {
|
|
21549
21799
|
type: "boolean",
|
|
21550
21800
|
default: true,
|