@lukoweb/apitogo 0.1.81 → 0.1.83
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 +292 -122
- package/docs/cli/commands.md +2 -2
- package/docs/cli/overview.md +6 -5
- package/docs/cli/setup.md +5 -5
- package/docs/cli/shape.md +9 -7
- package/docs/configuration/authentication.md +1 -1
- package/docs/configuration/make-config.mdx +23 -15
- package/docs/configuration/manifest.md +1 -1
- package/docs/configuration/overview.md +1 -1
- package/docs/configuration/plans.md +4 -4
- package/docs/guides/custom-pages.md +2 -2
- package/package.json +1 -1
package/dist/cli/cli.js
CHANGED
|
@@ -5633,7 +5633,7 @@ import {
|
|
|
5633
5633
|
// package.json
|
|
5634
5634
|
var package_default = {
|
|
5635
5635
|
name: "@lukoweb/apitogo",
|
|
5636
|
-
version: "0.1.
|
|
5636
|
+
version: "0.1.83",
|
|
5637
5637
|
type: "module",
|
|
5638
5638
|
sideEffects: [
|
|
5639
5639
|
"**/*.css",
|
|
@@ -7454,13 +7454,13 @@ var generateCode = (schema2, filePath) => {
|
|
|
7454
7454
|
|
|
7455
7455
|
// src/vite/api/SchemaManager.ts
|
|
7456
7456
|
var FALLBACK_VERSION = "default";
|
|
7457
|
-
var parseInputString = (
|
|
7458
|
-
const idx =
|
|
7457
|
+
var parseInputString = (input3) => {
|
|
7458
|
+
const idx = input3.indexOf("?");
|
|
7459
7459
|
if (idx === -1)
|
|
7460
|
-
return { filePath:
|
|
7460
|
+
return { filePath: input3, params: {} };
|
|
7461
7461
|
return {
|
|
7462
|
-
filePath:
|
|
7463
|
-
params: Object.fromEntries(new URLSearchParams(
|
|
7462
|
+
filePath: input3.slice(0, idx),
|
|
7463
|
+
params: Object.fromEntries(new URLSearchParams(input3.slice(idx)))
|
|
7464
7464
|
};
|
|
7465
7465
|
};
|
|
7466
7466
|
var canonicalParams = (params) => Object.entries(params).sort(([a], [b]) => a.localeCompare(b)).map(([k, v]) => `${k}=${v}`).join("&");
|
|
@@ -7496,8 +7496,8 @@ var SchemaManager = class {
|
|
|
7496
7496
|
...processors
|
|
7497
7497
|
];
|
|
7498
7498
|
}
|
|
7499
|
-
getPathForFile = (
|
|
7500
|
-
const filePath = path11.resolve(this.config.__meta.rootDir,
|
|
7499
|
+
getPathForFile = (input3, params) => {
|
|
7500
|
+
const filePath = path11.resolve(this.config.__meta.rootDir, input3);
|
|
7501
7501
|
const apis = ensureArray(this.config.apis ?? []);
|
|
7502
7502
|
for (const apiConfig of apis) {
|
|
7503
7503
|
if (!apiConfig || apiConfig.type !== "file" || !apiConfig.path) continue;
|
|
@@ -7507,12 +7507,12 @@ var SchemaManager = class {
|
|
|
7507
7507
|
if (match) return apiConfig.path;
|
|
7508
7508
|
}
|
|
7509
7509
|
};
|
|
7510
|
-
processSchema = async (
|
|
7511
|
-
const filePath = path11.resolve(this.config.__meta.rootDir,
|
|
7512
|
-
const params =
|
|
7513
|
-
const configuredPath = this.getPathForFile(
|
|
7510
|
+
processSchema = async (input3) => {
|
|
7511
|
+
const filePath = path11.resolve(this.config.__meta.rootDir, input3.input);
|
|
7512
|
+
const params = input3.params;
|
|
7513
|
+
const configuredPath = this.getPathForFile(input3.input, params);
|
|
7514
7514
|
if (!configuredPath) {
|
|
7515
|
-
console.warn(`No path found for file ${
|
|
7515
|
+
console.warn(`No path found for file ${input3.input}`);
|
|
7516
7516
|
return;
|
|
7517
7517
|
}
|
|
7518
7518
|
const parser = new $RefParser2();
|
|
@@ -7619,20 +7619,20 @@ var SchemaManager = class {
|
|
|
7619
7619
|
if (apiConfig.type !== "file" || !apiConfig.path) continue;
|
|
7620
7620
|
const inputs = normalizeInputs(apiConfig.input);
|
|
7621
7621
|
if (inputs.length === 0) throw new Error("No schema found");
|
|
7622
|
-
this.processedSchemas[apiConfig.path] = inputs.map((
|
|
7622
|
+
this.processedSchemas[apiConfig.path] = inputs.map((input3) => ({
|
|
7623
7623
|
schema: {},
|
|
7624
7624
|
version: "",
|
|
7625
|
-
path:
|
|
7626
|
-
label:
|
|
7627
|
-
inputPath: path11.resolve(this.config.__meta.rootDir,
|
|
7628
|
-
params:
|
|
7625
|
+
path: input3.path ?? "",
|
|
7626
|
+
label: input3.label,
|
|
7627
|
+
inputPath: path11.resolve(this.config.__meta.rootDir, input3.input),
|
|
7628
|
+
params: input3.params,
|
|
7629
7629
|
importKey: "",
|
|
7630
7630
|
downloadUrl: "",
|
|
7631
7631
|
processedJsonPath: "",
|
|
7632
7632
|
processedTime: 0
|
|
7633
7633
|
}));
|
|
7634
7634
|
const results = await Promise.allSettled(
|
|
7635
|
-
inputs.map((
|
|
7635
|
+
inputs.map((input3) => this.processSchema(input3))
|
|
7636
7636
|
);
|
|
7637
7637
|
const errors = results.flatMap(
|
|
7638
7638
|
(r) => r.status === "rejected" ? r.reason : []
|
|
@@ -7711,7 +7711,7 @@ import { readFile } from "node:fs/promises";
|
|
|
7711
7711
|
import matter from "gray-matter";
|
|
7712
7712
|
import { parse, stringify as stringify2 } from "yaml";
|
|
7713
7713
|
var yaml = {
|
|
7714
|
-
parse: (
|
|
7714
|
+
parse: (input3) => parse(input3) ?? {},
|
|
7715
7715
|
stringify: (obj) => stringify2(obj)
|
|
7716
7716
|
};
|
|
7717
7717
|
var readFrontmatter = async (filePath) => {
|
|
@@ -10520,6 +10520,7 @@ var build_default = {
|
|
|
10520
10520
|
import { constants as constants2 } from "node:fs";
|
|
10521
10521
|
import { access as access3, mkdir as mkdir7, readFile as readFile5, writeFile as writeFile7 } from "node:fs/promises";
|
|
10522
10522
|
import path36 from "node:path";
|
|
10523
|
+
import { stdin as input2 } from "node:process";
|
|
10523
10524
|
import { glob as glob5 } from "glob";
|
|
10524
10525
|
|
|
10525
10526
|
// src/cli/make-config/interactive.ts
|
|
@@ -10783,12 +10784,12 @@ function mergeDocumentationNavigation(navigation, docRoutePaths, options) {
|
|
|
10783
10784
|
}
|
|
10784
10785
|
return [documentation, ...rest];
|
|
10785
10786
|
}
|
|
10786
|
-
function applyOpenApiToJsonConfig(config2,
|
|
10787
|
+
function applyOpenApiToJsonConfig(config2, input3) {
|
|
10787
10788
|
const next = { ...config2 };
|
|
10788
10789
|
next.apis = [
|
|
10789
10790
|
{
|
|
10790
10791
|
type: "file",
|
|
10791
|
-
input:
|
|
10792
|
+
input: input3,
|
|
10792
10793
|
path: "api"
|
|
10793
10794
|
}
|
|
10794
10795
|
];
|
|
@@ -10831,15 +10832,15 @@ function buildLimitsJson(rateLimitCalls, monthlyQuota) {
|
|
|
10831
10832
|
quota: { calls: monthlyQuota, renewalPeriodDays: 30 }
|
|
10832
10833
|
});
|
|
10833
10834
|
}
|
|
10834
|
-
function buildPlanItemFromWizard(
|
|
10835
|
-
const sku =
|
|
10835
|
+
function buildPlanItemFromWizard(input3) {
|
|
10836
|
+
const sku = input3.sku ?? slugifyPlanSku(input3.displayName);
|
|
10836
10837
|
const item = {
|
|
10837
10838
|
sku,
|
|
10838
|
-
displayName:
|
|
10839
|
-
priceAmount:
|
|
10840
|
-
limitsJson: buildLimitsJson(
|
|
10839
|
+
displayName: input3.displayName,
|
|
10840
|
+
priceAmount: input3.priceAmount,
|
|
10841
|
+
limitsJson: buildLimitsJson(input3.rateLimitCalls, input3.monthlyQuota)
|
|
10841
10842
|
};
|
|
10842
|
-
if (
|
|
10843
|
+
if (input3.priceAmount > 0) {
|
|
10843
10844
|
item.priceCurrency = "usd";
|
|
10844
10845
|
item.billingPeriod = "month";
|
|
10845
10846
|
}
|
|
@@ -10870,6 +10871,45 @@ function applyPlansToJsonConfig(config2, planItems) {
|
|
|
10870
10871
|
modules.userPanel = userPanel;
|
|
10871
10872
|
return { ...config2, modules };
|
|
10872
10873
|
}
|
|
10874
|
+
var BACKEND_RUNTIMES = ["dotnet", "nodejs", "python"];
|
|
10875
|
+
var RUNTIME_VERSIONS_BY_RUNTIME = {
|
|
10876
|
+
nodejs: ["NODE_22_LTS", "NODE_24_LTS"],
|
|
10877
|
+
python: [
|
|
10878
|
+
"PYTHON_310",
|
|
10879
|
+
"PYTHON_311",
|
|
10880
|
+
"PYTHON_312",
|
|
10881
|
+
"PYTHON_313",
|
|
10882
|
+
"PYTHON_314"
|
|
10883
|
+
],
|
|
10884
|
+
dotnet: ["DOTNET_8", "DOTNET_9", "DOTNET_10"]
|
|
10885
|
+
};
|
|
10886
|
+
function parseBackendRuntime(raw) {
|
|
10887
|
+
const value = raw.trim().toLowerCase();
|
|
10888
|
+
if (value === "node" || value === "nodejs") {
|
|
10889
|
+
return "nodejs";
|
|
10890
|
+
}
|
|
10891
|
+
if (value === "dotnet" || value === "python") {
|
|
10892
|
+
return value;
|
|
10893
|
+
}
|
|
10894
|
+
return null;
|
|
10895
|
+
}
|
|
10896
|
+
function parseRuntimeVersion(runtime, raw) {
|
|
10897
|
+
const value = raw.trim().toUpperCase();
|
|
10898
|
+
const allowed = RUNTIME_VERSIONS_BY_RUNTIME[runtime];
|
|
10899
|
+
return allowed.includes(value) ? value : null;
|
|
10900
|
+
}
|
|
10901
|
+
function applyBackendToJsonConfig(config2, backend) {
|
|
10902
|
+
const existing = isPlainObject2(config2.backend) ? config2.backend : {};
|
|
10903
|
+
return {
|
|
10904
|
+
...config2,
|
|
10905
|
+
backend: {
|
|
10906
|
+
...existing,
|
|
10907
|
+
sourceDirectory: backend.sourceDirectory,
|
|
10908
|
+
runtime: backend.runtime,
|
|
10909
|
+
runtimeVersion: backend.runtimeVersion
|
|
10910
|
+
}
|
|
10911
|
+
};
|
|
10912
|
+
}
|
|
10873
10913
|
function toRoutePath(relativeFile, pagesDir) {
|
|
10874
10914
|
const relativeNoExt = relativeFile.replace(/\.(mdx?|tsx|jsx)$/i, "");
|
|
10875
10915
|
const escaped = pagesDir.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
@@ -10897,6 +10937,17 @@ export default buildApitogoConfig({
|
|
|
10897
10937
|
function isThinConfigWrapper(source) {
|
|
10898
10938
|
return /export\s+default\s+buildApitogoConfig\s*\(/.test(source);
|
|
10899
10939
|
}
|
|
10940
|
+
function isManagedConfigWrapper(source) {
|
|
10941
|
+
if (isThinConfigWrapper(source)) {
|
|
10942
|
+
return true;
|
|
10943
|
+
}
|
|
10944
|
+
return /from\s+["']\.\/apitogo\.config\.json["']/.test(source) && /buildApitogoConfig\s*\(/.test(source) && /type:\s*["']custom-page["']/.test(source);
|
|
10945
|
+
}
|
|
10946
|
+
function labelFromRoutePath(routePath) {
|
|
10947
|
+
const slug = routePath.replace(/^\/+/, "");
|
|
10948
|
+
const leaf = slug.split("/").filter(Boolean).pop() ?? slug;
|
|
10949
|
+
return titleFromDirectoryName(leaf);
|
|
10950
|
+
}
|
|
10900
10951
|
function buildThinWrapperWithCustomPages(tsxRoutePaths) {
|
|
10901
10952
|
if (tsxRoutePaths.length === 0) {
|
|
10902
10953
|
return THIN_WRAPPER_SOURCE;
|
|
@@ -10909,9 +10960,11 @@ function buildThinWrapperWithCustomPages(tsxRoutePaths) {
|
|
|
10909
10960
|
const customPages = tsxRoutePaths.map((routePath) => {
|
|
10910
10961
|
const slug = routePath.replace(/^\//, "");
|
|
10911
10962
|
const name = pageComponentName(slug);
|
|
10963
|
+
const label = labelFromRoutePath(routePath);
|
|
10912
10964
|
return ` {
|
|
10913
10965
|
type: "custom-page",
|
|
10914
10966
|
path: ${JSON.stringify(routePath)},
|
|
10967
|
+
label: ${JSON.stringify(label)},
|
|
10915
10968
|
element: <${name} />,
|
|
10916
10969
|
},`;
|
|
10917
10970
|
}).join("\n");
|
|
@@ -10933,13 +10986,17 @@ export default {
|
|
|
10933
10986
|
label: ${JSON.stringify(DOCUMENTATION_NAV_LABEL)},
|
|
10934
10987
|
items: [
|
|
10935
10988
|
${customPages}
|
|
10936
|
-
...(((config.navigation ?? []).find(
|
|
10989
|
+
...((((config.navigation ?? []).find(
|
|
10937
10990
|
(item) =>
|
|
10938
10991
|
typeof item === "object" &&
|
|
10939
10992
|
item !== null &&
|
|
10940
10993
|
"label" in item &&
|
|
10941
10994
|
item.label === ${JSON.stringify(DOCUMENTATION_NAV_LABEL)},
|
|
10942
|
-
) as { items?: unknown[] } | undefined)?.items ?? [])
|
|
10995
|
+
) as { items?: unknown[] } | undefined)?.items ?? []).filter(
|
|
10996
|
+
(item) =>
|
|
10997
|
+
typeof item !== "string" ||
|
|
10998
|
+
!${JSON.stringify(tsxRoutePaths)}.includes(item),
|
|
10999
|
+
)),
|
|
10943
11000
|
],
|
|
10944
11001
|
},
|
|
10945
11002
|
...(config.navigation ?? []).filter(
|
|
@@ -11022,45 +11079,95 @@ var parseLoopChoice = (raw) => {
|
|
|
11022
11079
|
if (!value || value === "done") {
|
|
11023
11080
|
return "done";
|
|
11024
11081
|
}
|
|
11025
|
-
if (value === "openapi" || value === "page" || value === "plan") {
|
|
11082
|
+
if (value === "backend" || value === "openapi" || value === "page" || value === "plan") {
|
|
11026
11083
|
return value;
|
|
11027
11084
|
}
|
|
11028
11085
|
return null;
|
|
11029
11086
|
};
|
|
11030
|
-
async function promptMakeConfigInteractive(
|
|
11087
|
+
async function promptMakeConfigInteractive(options) {
|
|
11031
11088
|
const rl = createInterface({ input, output });
|
|
11032
|
-
const customPages = [];
|
|
11033
|
-
const planItems = [];
|
|
11034
11089
|
let hasPaidPlans = false;
|
|
11035
|
-
let openApiPath;
|
|
11036
11090
|
try {
|
|
11037
|
-
const title = await ask(rl, "Site title", defaults.title);
|
|
11038
11091
|
while (true) {
|
|
11039
11092
|
const choiceRaw = await ask(
|
|
11040
11093
|
rl,
|
|
11041
|
-
"
|
|
11094
|
+
"What do you want to configure? (backend / openapi / page / plan / done)",
|
|
11042
11095
|
"done"
|
|
11043
11096
|
);
|
|
11044
11097
|
const choice = parseLoopChoice(choiceRaw);
|
|
11045
11098
|
if (!choice) {
|
|
11046
|
-
printDiagnosticsToConsole(
|
|
11099
|
+
printDiagnosticsToConsole(
|
|
11100
|
+
"Choose backend, openapi, page, plan, or done."
|
|
11101
|
+
);
|
|
11047
11102
|
continue;
|
|
11048
11103
|
}
|
|
11049
11104
|
if (choice === "done") {
|
|
11050
11105
|
break;
|
|
11051
11106
|
}
|
|
11107
|
+
if (choice === "backend") {
|
|
11108
|
+
const sourceDirectory = await ask(
|
|
11109
|
+
rl,
|
|
11110
|
+
"Backend source directory (relative to portal)",
|
|
11111
|
+
".."
|
|
11112
|
+
);
|
|
11113
|
+
if (!sourceDirectory.trim()) {
|
|
11114
|
+
printDiagnosticsToConsole("Backend source directory is required.");
|
|
11115
|
+
continue;
|
|
11116
|
+
}
|
|
11117
|
+
let runtime = null;
|
|
11118
|
+
while (!runtime) {
|
|
11119
|
+
const runtimeRaw = await ask(
|
|
11120
|
+
rl,
|
|
11121
|
+
`Runtime (${BACKEND_RUNTIMES.join(" / ")})`,
|
|
11122
|
+
"nodejs"
|
|
11123
|
+
);
|
|
11124
|
+
runtime = parseBackendRuntime(runtimeRaw);
|
|
11125
|
+
if (!runtime) {
|
|
11126
|
+
printDiagnosticsToConsole(
|
|
11127
|
+
`Choose one of: ${BACKEND_RUNTIMES.join(", ")} (node \u2192 nodejs).`
|
|
11128
|
+
);
|
|
11129
|
+
}
|
|
11130
|
+
}
|
|
11131
|
+
const versions = RUNTIME_VERSIONS_BY_RUNTIME[runtime];
|
|
11132
|
+
let runtimeVersion = null;
|
|
11133
|
+
while (!runtimeVersion) {
|
|
11134
|
+
const versionRaw = await ask(
|
|
11135
|
+
rl,
|
|
11136
|
+
`Runtime version (${versions.join(" / ")})`,
|
|
11137
|
+
versions[0]
|
|
11138
|
+
);
|
|
11139
|
+
runtimeVersion = parseRuntimeVersion(runtime, versionRaw);
|
|
11140
|
+
if (!runtimeVersion) {
|
|
11141
|
+
printDiagnosticsToConsole(
|
|
11142
|
+
`Choose one of: ${versions.join(", ")}.`
|
|
11143
|
+
);
|
|
11144
|
+
}
|
|
11145
|
+
}
|
|
11146
|
+
const backend = {
|
|
11147
|
+
sourceDirectory: sourceDirectory.trim(),
|
|
11148
|
+
runtime,
|
|
11149
|
+
runtimeVersion
|
|
11150
|
+
};
|
|
11151
|
+
await options.onApplied({ kind: "backend", backend });
|
|
11152
|
+
printDiagnosticsToConsole(
|
|
11153
|
+
`Backend saved: ${backend.runtime} ${backend.runtimeVersion} (sourceDirectory: ${backend.sourceDirectory}).`
|
|
11154
|
+
);
|
|
11155
|
+
continue;
|
|
11156
|
+
}
|
|
11052
11157
|
if (choice === "openapi") {
|
|
11053
11158
|
const entered = await ask(
|
|
11054
11159
|
rl,
|
|
11055
11160
|
"OpenAPI file path (leave empty to skip)",
|
|
11056
|
-
|
|
11161
|
+
options.discoveredOpenApi ?? ""
|
|
11057
11162
|
);
|
|
11058
|
-
|
|
11059
|
-
|
|
11060
|
-
|
|
11061
|
-
`OpenAPI will be linked as a relative path in apitogo.config.json.`
|
|
11062
|
-
);
|
|
11163
|
+
if (!entered) {
|
|
11164
|
+
printDiagnosticsToConsole("OpenAPI skipped.");
|
|
11165
|
+
continue;
|
|
11063
11166
|
}
|
|
11167
|
+
await options.onApplied({ kind: "openapi", openApiPath: entered });
|
|
11168
|
+
printDiagnosticsToConsole(
|
|
11169
|
+
`OpenAPI linked as a relative path in apitogo.config.json.`
|
|
11170
|
+
);
|
|
11064
11171
|
continue;
|
|
11065
11172
|
}
|
|
11066
11173
|
if (choice === "page") {
|
|
@@ -11079,14 +11186,14 @@ async function promptMakeConfigInteractive(defaults) {
|
|
|
11079
11186
|
continue;
|
|
11080
11187
|
}
|
|
11081
11188
|
const routePath = `/${slug}`;
|
|
11082
|
-
const relativePath = path34.posix.join(
|
|
11189
|
+
const relativePath = path34.posix.join(options.pagesDir, `${slug}.tsx`);
|
|
11083
11190
|
const page = {
|
|
11084
11191
|
slug,
|
|
11085
11192
|
title: pageTitle.trim(),
|
|
11086
11193
|
routePath,
|
|
11087
11194
|
relativePath
|
|
11088
11195
|
};
|
|
11089
|
-
|
|
11196
|
+
await options.onApplied({ kind: "page", page });
|
|
11090
11197
|
printPageAddedHint(relativePath, routePath);
|
|
11091
11198
|
continue;
|
|
11092
11199
|
}
|
|
@@ -11116,25 +11223,15 @@ async function promptMakeConfigInteractive(defaults) {
|
|
|
11116
11223
|
rateLimitCalls,
|
|
11117
11224
|
monthlyQuota
|
|
11118
11225
|
});
|
|
11119
|
-
|
|
11120
|
-
if (
|
|
11226
|
+
const paid = priceAmount > 0;
|
|
11227
|
+
if (paid) {
|
|
11121
11228
|
hasPaidPlans = true;
|
|
11122
11229
|
printPaidPlanPlatformWarning();
|
|
11123
11230
|
}
|
|
11231
|
+
await options.onApplied({ kind: "plan", planItem, paid });
|
|
11124
11232
|
printPlanAddedHint(displayName.trim());
|
|
11125
11233
|
}
|
|
11126
|
-
return {
|
|
11127
|
-
title,
|
|
11128
|
-
site: { title },
|
|
11129
|
-
authentication: {
|
|
11130
|
-
enabled: true,
|
|
11131
|
-
type: "dev-portal"
|
|
11132
|
-
},
|
|
11133
|
-
openApiPath,
|
|
11134
|
-
customPages,
|
|
11135
|
-
planItems,
|
|
11136
|
-
hasPaidPlans
|
|
11137
|
-
};
|
|
11234
|
+
return { hasPaidPlans };
|
|
11138
11235
|
} finally {
|
|
11139
11236
|
rl.close();
|
|
11140
11237
|
}
|
|
@@ -11414,11 +11511,66 @@ var readJsonConfig = async (filePath) => {
|
|
|
11414
11511
|
}
|
|
11415
11512
|
return parsed;
|
|
11416
11513
|
};
|
|
11514
|
+
function shouldRunInteractive(argv) {
|
|
11515
|
+
if (argv.interactive === true) {
|
|
11516
|
+
return true;
|
|
11517
|
+
}
|
|
11518
|
+
if (argv.interactive === false) {
|
|
11519
|
+
return false;
|
|
11520
|
+
}
|
|
11521
|
+
const hasPatchFlags = argv.title !== void 0 || argv.site !== void 0 || argv.theme !== void 0 || argv.authentication !== void 0 || argv.force === true;
|
|
11522
|
+
if (hasPatchFlags) {
|
|
11523
|
+
return false;
|
|
11524
|
+
}
|
|
11525
|
+
return input2.isTTY === true;
|
|
11526
|
+
}
|
|
11527
|
+
async function persistConfig(config2, ctx) {
|
|
11528
|
+
const pageFiles = await glob5(`${ctx.pagesDir}/**/*.{md,mdx,tsx,jsx}`, {
|
|
11529
|
+
cwd: ctx.dir,
|
|
11530
|
+
ignore: ["**/node_modules/**", "**/.git/**", "**/dist/**"],
|
|
11531
|
+
posix: true
|
|
11532
|
+
});
|
|
11533
|
+
const markdownFiles = pageFiles.filter((file) => /\.mdx?$/i.test(file));
|
|
11534
|
+
const tsxFiles = pageFiles.filter((file) => /\.(tsx|jsx)$/i.test(file));
|
|
11535
|
+
const markdownRoutes = markdownFiles.map(
|
|
11536
|
+
(file) => toRoutePath(file, ctx.pagesDir)
|
|
11537
|
+
);
|
|
11538
|
+
const tsxRoutes = tsxFiles.map((file) => toRoutePath(file, ctx.pagesDir));
|
|
11539
|
+
const docRoutes = [.../* @__PURE__ */ new Set([...markdownRoutes, ...tsxRoutes])];
|
|
11540
|
+
if (docRoutes.length === 0) {
|
|
11541
|
+
printDiagnosticsToConsole(
|
|
11542
|
+
`No docs pages under '${ctx.pagesDir}/' \u2014 skipping documentation nav scan.`
|
|
11543
|
+
);
|
|
11544
|
+
}
|
|
11545
|
+
const next = {
|
|
11546
|
+
...config2,
|
|
11547
|
+
navigation: mergeDocumentationNavigation(
|
|
11548
|
+
config2.navigation,
|
|
11549
|
+
docRoutes,
|
|
11550
|
+
{ force: ctx.force }
|
|
11551
|
+
)
|
|
11552
|
+
};
|
|
11553
|
+
await writeFile7(ctx.jsonPath, `${JSON.stringify(next, null, 2)}
|
|
11554
|
+
`, "utf8");
|
|
11555
|
+
const tsxPath = path36.join(ctx.dir, "apitogo.config.tsx");
|
|
11556
|
+
if (await fileExists3(tsxPath) && tsxRoutes.length > 0) {
|
|
11557
|
+
const tsxSource = await readFile5(tsxPath, "utf8");
|
|
11558
|
+
if (isManagedConfigWrapper(tsxSource)) {
|
|
11559
|
+
await writeFile7(
|
|
11560
|
+
tsxPath,
|
|
11561
|
+
buildThinWrapperWithCustomPages(tsxRoutes),
|
|
11562
|
+
"utf8"
|
|
11563
|
+
);
|
|
11564
|
+
}
|
|
11565
|
+
}
|
|
11566
|
+
return next;
|
|
11567
|
+
}
|
|
11417
11568
|
async function makeConfig(argv) {
|
|
11418
11569
|
const dir = path36.resolve(process.cwd(), argv.dir);
|
|
11419
11570
|
const pagesDir = toPosixPath2(
|
|
11420
11571
|
argv.pagesDir.replace(/^[./]+/, "").replace(/\/+$/, "")
|
|
11421
11572
|
);
|
|
11573
|
+
const interactive = shouldRunInteractive(argv);
|
|
11422
11574
|
try {
|
|
11423
11575
|
const discoveredOpenApi = await findOpenApiSpecPath(dir);
|
|
11424
11576
|
const createdPackageJson = await ensurePackageJson(dir);
|
|
@@ -11455,79 +11607,71 @@ async function makeConfig(argv) {
|
|
|
11455
11607
|
authentication: parseJsonObjectFlag(argv.authentication, "authentication"),
|
|
11456
11608
|
title: argv.title
|
|
11457
11609
|
};
|
|
11458
|
-
|
|
11610
|
+
const persistCtx = {
|
|
11611
|
+
dir,
|
|
11612
|
+
pagesDir,
|
|
11613
|
+
jsonPath,
|
|
11614
|
+
force: argv.force === true
|
|
11615
|
+
};
|
|
11459
11616
|
let interactiveHasPaidPlans = false;
|
|
11460
|
-
if (
|
|
11461
|
-
|
|
11617
|
+
if (interactive) {
|
|
11618
|
+
config2 = await persistConfig(config2, persistCtx);
|
|
11462
11619
|
const prompted = await promptMakeConfigInteractive({
|
|
11463
|
-
title: flagPatches.title ?? (typeof site.title === "string" ? site.title : titleFromDirectoryName(path36.basename(dir))),
|
|
11464
11620
|
discoveredOpenApi,
|
|
11465
|
-
pagesDir
|
|
11621
|
+
pagesDir,
|
|
11622
|
+
onApplied: async (action2) => {
|
|
11623
|
+
if (action2.kind === "backend") {
|
|
11624
|
+
config2 = applyBackendToJsonConfig(config2, action2.backend);
|
|
11625
|
+
config2 = await persistConfig(config2, persistCtx);
|
|
11626
|
+
return;
|
|
11627
|
+
}
|
|
11628
|
+
if (action2.kind === "openapi") {
|
|
11629
|
+
const openApiInput = toRelativeOpenApiInput(
|
|
11630
|
+
dir,
|
|
11631
|
+
action2.openApiPath
|
|
11632
|
+
);
|
|
11633
|
+
config2 = applyOpenApiToJsonConfig(config2, openApiInput);
|
|
11634
|
+
config2 = await persistConfig(config2, persistCtx);
|
|
11635
|
+
return;
|
|
11636
|
+
}
|
|
11637
|
+
if (action2.kind === "page") {
|
|
11638
|
+
const pagePath = path36.join(dir, action2.page.relativePath);
|
|
11639
|
+
await mkdir7(path36.dirname(pagePath), { recursive: true });
|
|
11640
|
+
await writeFile7(
|
|
11641
|
+
pagePath,
|
|
11642
|
+
buildStarterDocsPageTsx(action2.page.slug, action2.page.title),
|
|
11643
|
+
"utf8"
|
|
11644
|
+
);
|
|
11645
|
+
config2 = await persistConfig(config2, persistCtx);
|
|
11646
|
+
return;
|
|
11647
|
+
}
|
|
11648
|
+
if (action2.kind === "plan") {
|
|
11649
|
+
config2 = applyPlansToJsonConfig(config2, [action2.planItem]);
|
|
11650
|
+
config2 = await persistConfig(config2, persistCtx);
|
|
11651
|
+
}
|
|
11652
|
+
}
|
|
11466
11653
|
});
|
|
11467
|
-
openApiOverride = prompted.openApiPath;
|
|
11468
11654
|
interactiveHasPaidPlans = prompted.hasPaidPlans;
|
|
11469
|
-
|
|
11470
|
-
config2 =
|
|
11471
|
-
|
|
11472
|
-
|
|
11473
|
-
|
|
11474
|
-
|
|
11475
|
-
pagePath,
|
|
11476
|
-
buildStarterDocsPageTsx(page.slug, page.title),
|
|
11477
|
-
"utf8"
|
|
11478
|
-
);
|
|
11479
|
-
}
|
|
11480
|
-
}
|
|
11481
|
-
config2 = applyConfigPatches(config2, flagPatches);
|
|
11482
|
-
const pageFiles = await glob5(`${pagesDir}/**/*.{md,mdx,tsx,jsx}`, {
|
|
11483
|
-
cwd: dir,
|
|
11484
|
-
ignore: ["**/node_modules/**", "**/.git/**", "**/dist/**"],
|
|
11485
|
-
posix: true
|
|
11486
|
-
});
|
|
11487
|
-
const markdownFiles = pageFiles.filter((file) => /\.mdx?$/i.test(file));
|
|
11488
|
-
const tsxFiles = pageFiles.filter((file) => /\.(tsx|jsx)$/i.test(file));
|
|
11489
|
-
const markdownRoutes = markdownFiles.map(
|
|
11490
|
-
(file) => toRoutePath(file, pagesDir)
|
|
11491
|
-
);
|
|
11492
|
-
const tsxRoutes = tsxFiles.map((file) => toRoutePath(file, pagesDir));
|
|
11493
|
-
if (markdownRoutes.length === 0 && tsxRoutes.length === 0) {
|
|
11494
|
-
printDiagnosticsToConsole(
|
|
11495
|
-
`No docs pages under '${pagesDir}/' \u2014 skipping documentation nav scan.`
|
|
11496
|
-
);
|
|
11497
|
-
}
|
|
11498
|
-
config2.navigation = mergeDocumentationNavigation(
|
|
11499
|
-
config2.navigation,
|
|
11500
|
-
markdownRoutes,
|
|
11501
|
-
{ force: argv.force === true }
|
|
11502
|
-
);
|
|
11503
|
-
const openApiRel = openApiOverride === void 0 ? discoveredOpenApi : openApiOverride;
|
|
11504
|
-
if (openApiRel) {
|
|
11505
|
-
const openApiInput = argv.interactive ? toRelativeOpenApiInput(dir, openApiRel) : openapiInputForConfig(openApiRel);
|
|
11506
|
-
config2 = applyOpenApiToJsonConfig(config2, openApiInput);
|
|
11507
|
-
}
|
|
11508
|
-
await writeFile7(jsonPath, `${JSON.stringify(config2, null, 2)}
|
|
11509
|
-
`, "utf8");
|
|
11510
|
-
const tsxPath = path36.join(dir, "apitogo.config.tsx");
|
|
11511
|
-
if (await fileExists3(tsxPath) && tsxRoutes.length > 0) {
|
|
11512
|
-
const tsxSource = await readFile5(tsxPath, "utf8");
|
|
11513
|
-
if (isThinConfigWrapper(tsxSource)) {
|
|
11514
|
-
await writeFile7(
|
|
11515
|
-
tsxPath,
|
|
11516
|
-
buildThinWrapperWithCustomPages(tsxRoutes),
|
|
11517
|
-
"utf8"
|
|
11655
|
+
} else {
|
|
11656
|
+
config2 = applyConfigPatches(config2, flagPatches);
|
|
11657
|
+
if (discoveredOpenApi) {
|
|
11658
|
+
config2 = applyOpenApiToJsonConfig(
|
|
11659
|
+
config2,
|
|
11660
|
+
openapiInputForConfig(discoveredOpenApi)
|
|
11518
11661
|
);
|
|
11519
11662
|
}
|
|
11663
|
+
config2 = await persistConfig(config2, persistCtx);
|
|
11520
11664
|
}
|
|
11521
11665
|
const action = createdJson ? "Initialized" : "Updated";
|
|
11522
11666
|
printResultToConsole(
|
|
11523
|
-
`${action} ${APITOGO_JSON_FILENAME}
|
|
11667
|
+
`${action} ${APITOGO_JSON_FILENAME}.`
|
|
11524
11668
|
);
|
|
11525
11669
|
if (createdPackageJson) {
|
|
11526
11670
|
printDiagnosticsToConsole(
|
|
11527
11671
|
"Next: run npm install (or pnpm install / yarn) in this directory, then npm run dev."
|
|
11528
11672
|
);
|
|
11529
11673
|
}
|
|
11530
|
-
if (
|
|
11674
|
+
if (interactive) {
|
|
11531
11675
|
printInteractiveWrapUp(interactiveHasPaidPlans);
|
|
11532
11676
|
}
|
|
11533
11677
|
} catch (error) {
|
|
@@ -11539,7 +11683,7 @@ async function makeConfig(argv) {
|
|
|
11539
11683
|
|
|
11540
11684
|
// src/cli/cmds/config.ts
|
|
11541
11685
|
var config_default = {
|
|
11542
|
-
desc: "Create or update apitogo.config.json
|
|
11686
|
+
desc: "Create or update apitogo.config.json (interactive on TTY: backend, OpenAPI, pages, plans)",
|
|
11543
11687
|
command: "config",
|
|
11544
11688
|
aliases: ["make-config"],
|
|
11545
11689
|
builder: (yargs2) => yargs2.option("dir", {
|
|
@@ -11555,8 +11699,7 @@ var config_default = {
|
|
|
11555
11699
|
}).option("interactive", {
|
|
11556
11700
|
alias: "i",
|
|
11557
11701
|
type: "boolean",
|
|
11558
|
-
describe: "Interactive wizard
|
|
11559
|
-
default: false
|
|
11702
|
+
describe: "Interactive wizard (default on TTY). Apply backend, OpenAPI, pages, and plans immediately. Use --no-interactive for CI/flags-only."
|
|
11560
11703
|
}).option("site", {
|
|
11561
11704
|
type: "string",
|
|
11562
11705
|
describe: "JSON object merged into site"
|
|
@@ -12005,10 +12148,36 @@ var dev_default = {
|
|
|
12005
12148
|
};
|
|
12006
12149
|
|
|
12007
12150
|
// src/cli/init/handler.ts
|
|
12151
|
+
import { mkdir as mkdir8, readdir, stat as stat4 } from "node:fs/promises";
|
|
12008
12152
|
import path40 from "node:path";
|
|
12153
|
+
var IGNORED_TARGET_ENTRIES = /* @__PURE__ */ new Set([".DS_Store", "Thumbs.db"]);
|
|
12154
|
+
async function ensureEmptyTargetDir(dir) {
|
|
12155
|
+
try {
|
|
12156
|
+
const existing = await stat4(dir);
|
|
12157
|
+
if (!existing.isDirectory()) {
|
|
12158
|
+
throw new Error(`${dir} exists and is not a directory.`);
|
|
12159
|
+
}
|
|
12160
|
+
} catch (err) {
|
|
12161
|
+
const code = err.code;
|
|
12162
|
+
if (code !== "ENOENT") {
|
|
12163
|
+
throw err;
|
|
12164
|
+
}
|
|
12165
|
+
await mkdir8(dir, { recursive: true });
|
|
12166
|
+
return;
|
|
12167
|
+
}
|
|
12168
|
+
const entries = (await readdir(dir)).filter(
|
|
12169
|
+
(name) => !IGNORED_TARGET_ENTRIES.has(name)
|
|
12170
|
+
);
|
|
12171
|
+
if (entries.length > 0) {
|
|
12172
|
+
throw new Error(
|
|
12173
|
+
`Directory '${dir}' is not empty. Choose a new directory or remove existing files.`
|
|
12174
|
+
);
|
|
12175
|
+
}
|
|
12176
|
+
}
|
|
12009
12177
|
async function init2(argv) {
|
|
12010
12178
|
const dir = path40.resolve(process.cwd(), argv.dir ?? ".");
|
|
12011
12179
|
try {
|
|
12180
|
+
await ensureEmptyTargetDir(dir);
|
|
12012
12181
|
const createdPackageJson = await ensurePackageJson(dir);
|
|
12013
12182
|
const createdTsconfig = await ensureTsconfigJson(dir);
|
|
12014
12183
|
await ensurePortalStubFiles(dir);
|
|
@@ -12023,10 +12192,11 @@ async function init2(argv) {
|
|
|
12023
12192
|
dir,
|
|
12024
12193
|
pagesDir: "pages",
|
|
12025
12194
|
title: argv.name,
|
|
12026
|
-
force: false
|
|
12195
|
+
force: false,
|
|
12196
|
+
interactive: false
|
|
12027
12197
|
});
|
|
12028
12198
|
printResultToConsole(
|
|
12029
|
-
`Portal scaffolded in ${dir}. Next: apitogo config
|
|
12199
|
+
`Portal scaffolded in ${dir}. Next: apitogo config, apitogo dev, then apitogo apply.`
|
|
12030
12200
|
);
|
|
12031
12201
|
} catch (error) {
|
|
12032
12202
|
await printCriticalFailureToConsoleAndExit(
|
package/docs/cli/commands.md
CHANGED
|
@@ -12,7 +12,7 @@ Commands are exposed by the **`apitogo`** binary (`@lukoweb/apitogo`). Platform
|
|
|
12
12
|
| Command | Role |
|
|
13
13
|
| ------- | ---- |
|
|
14
14
|
| `init` | Scaffold portal locally (no platform calls) |
|
|
15
|
-
| `config` / `make-config` | Write or update `apitogo.config.json` (interactive: OpenAPI, pages, plans) |
|
|
15
|
+
| `config` / `make-config` | Write or update `apitogo.config.json` (interactive: backend, OpenAPI, pages, plans) |
|
|
16
16
|
| `dev` | Local preview server |
|
|
17
17
|
| `build` | Production build (`--preview` to serve locally) |
|
|
18
18
|
| `apply` | Publish config to platform (apihub + portal) |
|
|
@@ -24,7 +24,7 @@ Commands are exposed by the **`apitogo`** binary (`@lukoweb/apitogo`). Platform
|
|
|
24
24
|
| Command | Role |
|
|
25
25
|
| ------- | ---- |
|
|
26
26
|
| `init [dir]` | Create `package.json`, stubs, and initial `apitogo.config.json` |
|
|
27
|
-
| `config` |
|
|
27
|
+
| `config` | Interactive on TTY: backend, OpenAPI, pages, plans (apply each immediately); flags for site/theme/auth |
|
|
28
28
|
|
|
29
29
|
`npm create @lukoweb/apitogo@latest` is an interactive alternative to `init` for new folders.
|
|
30
30
|
|
package/docs/cli/overview.md
CHANGED
|
@@ -14,7 +14,7 @@ local files, preview with `apitogo dev`, and publish to APItoGo hosting with `ap
|
|
|
14
14
|
## What you get
|
|
15
15
|
|
|
16
16
|
- Scaffold a local portal site (`npm create @lukoweb/apitogo@latest` or `apitogo init`)
|
|
17
|
-
- Shape OpenAPI, plans, docs pages
|
|
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
|
|
20
20
|
**Professional**
|
|
@@ -47,10 +47,11 @@ Common CLI shape tasks:
|
|
|
47
47
|
|
|
48
48
|
| Change | How |
|
|
49
49
|
| ----------------------------------- | ------------------------------------------------ |
|
|
50
|
-
|
|
|
51
|
-
|
|
|
52
|
-
|
|
|
53
|
-
|
|
|
50
|
+
| Backend runtime | `apitogo config` (backend) |
|
|
51
|
+
| OpenAPI + API Reference nav | `apitogo config` (openapi) |
|
|
52
|
+
| Custom docs pages | `apitogo config` (page) or add under `pages/` |
|
|
53
|
+
| Gateway / API plans | `apitogo config` (plan) or edit manifest |
|
|
54
|
+
| Site title | `--title` or edit `apitogo.config.json` |
|
|
54
55
|
| Theme, fonts, branding, projectId | Edit `apitogo.config.json` |
|
|
55
56
|
|
|
56
57
|
Home page content often lives in `landing/LandingPage.tsx`. Site footer can live under
|
package/docs/cli/setup.md
CHANGED
|
@@ -28,7 +28,7 @@ need a global install — scaffold commands add it to your project and `npm inst
|
|
|
28
28
|
```bash
|
|
29
29
|
npm create @lukoweb/apitogo@latest
|
|
30
30
|
cd your-project-name
|
|
31
|
-
npx apitogo config
|
|
31
|
+
npx apitogo config # interactive: backend, OpenAPI, pages, plans
|
|
32
32
|
npm run dev
|
|
33
33
|
```
|
|
34
34
|
|
|
@@ -40,7 +40,7 @@ Non-interactive example:
|
|
|
40
40
|
```bash
|
|
41
41
|
npm create @lukoweb/apitogo@latest my-portal -- --yes --typescript
|
|
42
42
|
cd my-portal
|
|
43
|
-
npx apitogo config
|
|
43
|
+
npx apitogo config
|
|
44
44
|
```
|
|
45
45
|
|
|
46
46
|
### Scaffold into an existing folder
|
|
@@ -52,18 +52,18 @@ before other `apitogo` commands:
|
|
|
52
52
|
npx @lukoweb/apitogo init ./dev-portal
|
|
53
53
|
cd ./dev-portal
|
|
54
54
|
npm install
|
|
55
|
-
npx apitogo config
|
|
55
|
+
npx apitogo config
|
|
56
56
|
npm run dev
|
|
57
57
|
```
|
|
58
58
|
|
|
59
|
-
`apitogo init` is local only — it does not call the platform. After `config
|
|
59
|
+
`apitogo init` is local only — it does not call the platform. After `config`, run `npm run dev`
|
|
60
60
|
to preview, then `apitogo apply` when ready to go live.
|
|
61
61
|
|
|
62
62
|
### How to run `apitogo`
|
|
63
63
|
|
|
64
64
|
| Situation | Command |
|
|
65
65
|
| --------- | ------- |
|
|
66
|
-
| After `npm create` or `npm install` in the project | `npx apitogo <command>` (e.g. `npx apitogo config
|
|
66
|
+
| After `npm create` or `npm install` in the project | `npx apitogo <command>` (e.g. `npx apitogo config`) |
|
|
67
67
|
| Same, if `node_modules/.bin` is on your PATH | `apitogo <command>` |
|
|
68
68
|
| One-off, no project yet | `npx @lukoweb/apitogo <command>` |
|
|
69
69
|
| Optional global install | `npm install -g @lukoweb/apitogo` then `apitogo <command>` anywhere |
|
package/docs/cli/shape.md
CHANGED
|
@@ -18,7 +18,7 @@ Requires the **`apitogo`** CLI (`@lukoweb/apitogo`). If you have not installed i
|
|
|
18
18
|
```bash
|
|
19
19
|
npm create @lukoweb/apitogo@latest my-portal -- --yes
|
|
20
20
|
cd my-portal
|
|
21
|
-
npx apitogo config
|
|
21
|
+
npx apitogo config # backend, OpenAPI, pages, plans (apply each immediately)
|
|
22
22
|
npm run dev # http://localhost:3000
|
|
23
23
|
```
|
|
24
24
|
|
|
@@ -28,18 +28,20 @@ Or scaffold into an existing folder (`npm install` is required after `init`):
|
|
|
28
28
|
npx @lukoweb/apitogo init ./dev-portal --name "Books API"
|
|
29
29
|
cd ./dev-portal
|
|
30
30
|
npm install
|
|
31
|
-
npx apitogo config
|
|
31
|
+
npx apitogo config
|
|
32
32
|
npm run dev
|
|
33
33
|
```
|
|
34
34
|
|
|
35
35
|
`apitogo init` does **not** call the platform. Run `apitogo apply` when you are ready to go live.
|
|
36
36
|
|
|
37
|
-
## Interactive config (`apitogo config
|
|
37
|
+
## Interactive config (`apitogo config`)
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
On a TTY, bare `apitogo config` opens the wizard. Each choice is written immediately; the loop
|
|
40
|
+
continues until you type `done`:
|
|
40
41
|
|
|
41
42
|
| Choice | What it does |
|
|
42
43
|
| ------ | ------------ |
|
|
44
|
+
| `backend` | Set `backend.sourceDirectory`, `runtime`, and platform `runtimeVersion` |
|
|
43
45
|
| `openapi` | Link an OpenAPI file by **relative path** in `apitogo.config.json` |
|
|
44
46
|
| `page` | Create `pages/<slug>.tsx` and wire it in navigation |
|
|
45
47
|
| `plan` | Add a gateway plan (title, price, rate limit, monthly quota) |
|
|
@@ -64,14 +66,14 @@ stubs when missing. Scans `pages/` for navigation and wires OpenAPI when a spec
|
|
|
64
66
|
npx apitogo config
|
|
65
67
|
npx apitogo config --title "Books API"
|
|
66
68
|
npx apitogo config --theme '{"light":{"primary":"#111111"}}'
|
|
67
|
-
npx apitogo config -
|
|
69
|
+
npx apitogo config --no-interactive
|
|
68
70
|
```
|
|
69
71
|
|
|
70
72
|
Common flags:
|
|
71
73
|
|
|
72
74
|
| Flag | Purpose |
|
|
73
75
|
| ---- | ------- |
|
|
74
|
-
| `--interactive` / `-i` |
|
|
76
|
+
| `--interactive` / `-i` | Force wizard (default on TTY). `--no-interactive` skips it |
|
|
75
77
|
| `--title` | Set `site.title` |
|
|
76
78
|
| `--theme`, `--site`, `--authentication` | Deep-merge JSON into config |
|
|
77
79
|
| `--pages-dir` | Folder to scan (default: `pages`) |
|
|
@@ -112,7 +114,7 @@ If you already have `pages/` and an OpenAPI spec:
|
|
|
112
114
|
```bash
|
|
113
115
|
npx @lukoweb/apitogo init .
|
|
114
116
|
npm install
|
|
115
|
-
npx apitogo config
|
|
117
|
+
npx apitogo config
|
|
116
118
|
npm run dev
|
|
117
119
|
```
|
|
118
120
|
|
|
@@ -30,7 +30,7 @@ Backend API URL (`apitogo apply` writes this to `.env` after the portal backend
|
|
|
30
30
|
VITE_APITOGO_AUTHENTICATION_API_BASE_URL=https://your-dev-portal-api.example.com
|
|
31
31
|
```
|
|
32
32
|
|
|
33
|
-
You do not need to enter this URL in `apitogo config
|
|
33
|
+
You do not need to enter this URL in `apitogo config`. Or set `authentication.apiBaseUrl` in JSON /
|
|
34
34
|
`APITOGO_CONFIG_AUTHENTICATION__API_BASE_URL` in `.env` manually.
|
|
35
35
|
|
|
36
36
|
### Local demo mode
|
|
@@ -10,7 +10,8 @@ description:
|
|
|
10
10
|
The **`apitogo config`** (alias: `make-config`) command writes **`apitogo.config.json`**. It can scaffold a portal
|
|
11
11
|
project (package.json, tsconfig, landing and user-panel stubs, thin `apitogo.config.tsx`) when those
|
|
12
12
|
files are missing, merge **site / theme / authentication** from flags, run an **interactive wizard**
|
|
13
|
-
|
|
13
|
+
on a TTY (apply each change immediately), scan `pages/` for navigation, and wire OpenAPI plus an
|
|
14
|
+
**API Reference** link.
|
|
14
15
|
|
|
15
16
|
Landing and user panel are **always enabled** on new configs. **Authentication is enabled by default**
|
|
16
17
|
on new scaffolds. The command does **not** change `redirects`.
|
|
@@ -34,31 +35,37 @@ Run config from the project root with `npx apitogo config` (or `apitogo config`
|
|
|
34
35
|
**`apitogo.config.tsx`** wrapper only if that file does not already exist (existing TSX is left
|
|
35
36
|
alone).
|
|
36
37
|
3. **Patches site, theme, and auth** from `--site`, `--theme`, `--authentication`, `--title`, or
|
|
37
|
-
|
|
38
|
+
the interactive wizard.
|
|
38
39
|
4. **Merges `navigation`** from `.md` / `.mdx` / `.tsx` files under your pages directory into a
|
|
39
40
|
Documentation category when those files exist. **Missing or empty `pages/` is fine** (common for
|
|
40
41
|
portal-only sites) — existing navigation is left alone. Other nav items are kept. Use
|
|
41
42
|
**`--force`** to rebuild Documentation items from disk when pages are present (API Reference is
|
|
42
43
|
still kept when OpenAPI is wired).
|
|
43
|
-
5. **Wires OpenAPI** when a spec is found (non-interactive) or when you enter a path in
|
|
44
|
+
5. **Wires OpenAPI** when a spec is found (non-interactive) or when you enter a path in the wizard:
|
|
44
45
|
`apis: [{ type: "file", input, path: "api" }]` and a top-level **API Reference** link to `/api` if
|
|
45
46
|
that link is missing. Interactive mode stores a **relative file path** in `apis[0].input` (for
|
|
46
47
|
example `./openapi.yaml` or `../api/openapi.yaml`).
|
|
47
|
-
6. **Interactive only** — can
|
|
48
|
-
|
|
48
|
+
6. **Interactive only** — can set **backend** (`sourceDirectory`, `runtime`, `runtimeVersion`),
|
|
49
|
+
scaffold **custom pages** under `pages/`, and append **gateway plans** to
|
|
50
|
+
`modules.userPanel.plans.items`. Each choice is written immediately.
|
|
49
51
|
|
|
50
|
-
## Interactive wizard
|
|
52
|
+
## Interactive wizard
|
|
53
|
+
|
|
54
|
+
On a TTY, bare `apitogo config` opens the wizard (no `-i` required). `-i` still works as an alias.
|
|
55
|
+
Use `--no-interactive` (or pass flags like `--title`) for CI / non-TTY.
|
|
51
56
|
|
|
52
57
|
```bash
|
|
53
|
-
npx apitogo config
|
|
58
|
+
npx apitogo config
|
|
54
59
|
```
|
|
55
60
|
|
|
56
|
-
1. **
|
|
57
|
-
|
|
61
|
+
1. **Loop** until you type `done` (default):
|
|
62
|
+
- **`backend`** — source directory, runtime (`dotnet` / `nodejs` / `python`), platform
|
|
63
|
+
`runtimeVersion` (writes `backend` in `apitogo.config.json` immediately)
|
|
58
64
|
- **`openapi`** — path to your OpenAPI file (relative link in config; empty skips)
|
|
59
|
-
- **`page`** — page title + URL path (writes `pages/<slug>.tsx` and wires navigation)
|
|
60
|
-
- **`plan`** — plan title, price (USD/month), rate limit (calls/min), monthly quota
|
|
61
|
-
|
|
65
|
+
- **`page`** — page title + URL path (writes `pages/<slug>.tsx` and wires navigation immediately)
|
|
66
|
+
- **`plan`** — plan title, price (USD/month), rate limit (calls/min), monthly quota (writes
|
|
67
|
+
immediately)
|
|
68
|
+
2. **Wrap-up** — links to [docs.apitogo.com](https://docs.apitogo.com) for further configuration
|
|
62
69
|
|
|
63
70
|
The wizard does **not** ask for fonts, colors, logo alt text, or the auth API base URL. Authentication
|
|
64
71
|
stays **enabled**; `apitogo apply` writes `VITE_APITOGO_AUTHENTICATION_API_BASE_URL` to `.env` after
|
|
@@ -83,8 +90,8 @@ npx apitogo config
|
|
|
83
90
|
| ------------------------- | --------------------------------------------------------------------------- |
|
|
84
91
|
| `--pages-dir <dir>` | Folder to scan for `.md` / `.mdx` / `.tsx` (default: `pages`) |
|
|
85
92
|
| `--dir <path>` | Project root (default: current directory) |
|
|
86
|
-
| `--interactive` / `-i` |
|
|
87
|
-
| `--title <string>` | Set `site.title`
|
|
93
|
+
| `--interactive` / `-i` | Force the wizard (default on TTY). Use `--no-interactive` to skip. |
|
|
94
|
+
| `--title <string>` | Set `site.title` (non-interactive) |
|
|
88
95
|
| `--site <json>` | Deep-merge a JSON object into `site` |
|
|
89
96
|
| `--theme <json>` | Deep-merge a JSON object into `theme` |
|
|
90
97
|
| `--authentication <json>` | Deep-merge a JSON object into `authentication` (secrets stay in `.env`) |
|
|
@@ -93,9 +100,10 @@ npx apitogo config
|
|
|
93
100
|
Examples:
|
|
94
101
|
|
|
95
102
|
```bash
|
|
103
|
+
npx apitogo config
|
|
96
104
|
npx apitogo config --title "Books API"
|
|
97
105
|
npx apitogo config --theme '{"light":{"primary":"#111111"}}'
|
|
98
|
-
npx apitogo config -
|
|
106
|
+
npx apitogo config --no-interactive
|
|
99
107
|
```
|
|
100
108
|
|
|
101
109
|
Theme and fonts are edited in `apitogo.config.json` or via MCP — not in the interactive wizard. See
|
|
@@ -57,7 +57,7 @@ metadata, with optional `.env` overrides for secrets and local tweaks.
|
|
|
57
57
|
- **Site name / logo:** only `site.title` and `site.logo` (platform branding is derived at publish).
|
|
58
58
|
- **OpenAPI path:** `backend.openApiPath` is derived from `apis[0].input`.
|
|
59
59
|
- **Billing / account plans:** use `modules.userPanel.plans.enabled` and store the plan catalog in
|
|
60
|
-
`modules.userPanel.plans.items`. Add plans with `apitogo config
|
|
60
|
+
`modules.userPanel.plans.items`. Add plans with `apitogo config` or edit the manifest by hand.
|
|
61
61
|
See [Gateway plans](./plans.md). Legacy root `plans` is migrated on load.
|
|
62
62
|
- **Docs / API keys:** configure under `modules.docs` and `modules.userPanel.apiKeys` only.
|
|
63
63
|
|
|
@@ -160,7 +160,7 @@ Put navigation in `apitogo.config.json`, or keep large trees in a separate modul
|
|
|
160
160
|
## Related
|
|
161
161
|
|
|
162
162
|
- [Manifest (`apitogo.config.json`)](./manifest.md)
|
|
163
|
-
- [Make config (CLI)](./make-config.mdx) — interactive wizard (`apitogo config
|
|
163
|
+
- [Make config (CLI)](./make-config.mdx) — interactive wizard (`apitogo config`)
|
|
164
164
|
- [Gateway plans](./plans.md)
|
|
165
165
|
- [Authentication](./authentication.md)
|
|
166
166
|
- [MCP overview](../mcp/overview.md)
|
|
@@ -14,12 +14,12 @@ Store the catalog under `modules.userPanel.plans.items` in `apitogo.config.json`
|
|
|
14
14
|
|
|
15
15
|
## Shape with the CLI
|
|
16
16
|
|
|
17
|
-
`apitogo config
|
|
18
|
-
also edit the manifest by hand or use MCP `update_dev_portal_manifest`.
|
|
17
|
+
`apitogo config` can add plans interactively (title, price, rate limit, monthly quota). Each plan is
|
|
18
|
+
written immediately. You can also edit the manifest by hand or use MCP `update_dev_portal_manifest`.
|
|
19
19
|
|
|
20
20
|
```bash
|
|
21
|
-
npx apitogo config
|
|
22
|
-
#
|
|
21
|
+
npx apitogo config
|
|
22
|
+
# choose plan → enter title, price, policies → done when finished
|
|
23
23
|
```
|
|
24
24
|
|
|
25
25
|
## Canonical shape
|
|
@@ -13,8 +13,8 @@ Start by creating the page you want to add.
|
|
|
13
13
|
|
|
14
14
|
## CLI scaffold
|
|
15
15
|
|
|
16
|
-
`apitogo config
|
|
17
|
-
`page` in the wizard loop, then edit the generated `.tsx` file.
|
|
16
|
+
`apitogo config` can create a starter page under `pages/` and wire it into navigation immediately.
|
|
17
|
+
Choose `page` in the wizard loop, then edit the generated `.tsx` file.
|
|
18
18
|
|
|
19
19
|
Predefined UI components (Callout, Card, Button, Heading, and more) are documented under
|
|
20
20
|
[/docs/components](/docs/components).
|