@layerall/cli 0.1.0
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/LICENSE +21 -0
- package/README.md +63 -0
- package/dist/chunk-WDABYCHT.js +204 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +106 -0
- package/dist/index.d.ts +77 -0
- package/dist/index.js +14 -0
- package/dist/templates/landing.html +479 -0
- package/dist/templates/policy-schema.md +19 -0
- package/dist/templates/templates/landing.html +479 -0
- package/dist/templates/templates/policy-schema.md +19 -0
- package/package.json +67 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Sidarta Veloso
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# @layerall/cli
|
|
2
|
+
|
|
3
|
+
CLI tool for scaffolding LayerAll policies and validating them.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g @layerall/cli
|
|
9
|
+
# or
|
|
10
|
+
npx @layerall/cli <command>
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Commands
|
|
14
|
+
|
|
15
|
+
### `layerall init`
|
|
16
|
+
|
|
17
|
+
Scaffolds a `layerall.policy.json` with sensible defaults covering all four operations.
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
layerall init -p providerA,providerB,providerC -w providerA=50,providerB=30,providerC=20 -o layerall.policy.json
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### `layerall validate <file>`
|
|
24
|
+
|
|
25
|
+
Validates the shape of a policy document and reports issues.
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
layerall validate layerall.policy.json
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### `layerall init-landing`
|
|
32
|
+
|
|
33
|
+
Generates a static marketing landing page (`index.html`) from a JSON config describing
|
|
34
|
+
the product, providers, strategies, pricing and SDK snippets. With no `--config`,
|
|
35
|
+
emits a brandable agnostic "AllX" template.
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
layerall init-landing --config allgeo.json --out landing-geo
|
|
39
|
+
layerall init-landing --out landing-default
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Schema:
|
|
43
|
+
|
|
44
|
+
```jsonc
|
|
45
|
+
{
|
|
46
|
+
"product": "AllGeo", // required
|
|
47
|
+
"tagline": "Geocode reverso unificado", // required
|
|
48
|
+
"domain": "geocode reverso", // required
|
|
49
|
+
"operations": ["reverse", "batch"], // required
|
|
50
|
+
"strategies": ["round_robin", "most_fast", "failover"], // required
|
|
51
|
+
"providers": [ // required (≥1)
|
|
52
|
+
{ "name": "Google Maps", "weight": 50, "latency": 180, "health": 0.98 }
|
|
53
|
+
],
|
|
54
|
+
"sdkExamples": { /* "node" | "python" | "curl": "..." */ }, // optional
|
|
55
|
+
"pricing": { /* "<tier>": { "price": "...", "requests": "...", "providers": number | "ilimitado", "features": ["..."] } */ }, // optional
|
|
56
|
+
"cta": { "email": "geo@allx.com" } // optional
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
The generated HTML is fully static (Tailwind CDN + Google Fonts, no build step) and
|
|
61
|
+
can be hosted on any CDN.
|
|
62
|
+
|
|
63
|
+
See the root [README](../../README.md).
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
// src/policy.ts
|
|
2
|
+
var ALL_OPS = ["create", "send", "status", "cancel"];
|
|
3
|
+
var STRATEGIES_PER_OP = {
|
|
4
|
+
create: "round_robin",
|
|
5
|
+
send: "load_balance",
|
|
6
|
+
status: "most_fast",
|
|
7
|
+
cancel: "failover"
|
|
8
|
+
};
|
|
9
|
+
function buildPolicy(opts) {
|
|
10
|
+
const providers = opts.providers.slice();
|
|
11
|
+
const failures = { max: opts.retries?.max ?? 1, backoffMs: opts.retries?.backoffMs ?? 300 };
|
|
12
|
+
const operations = {};
|
|
13
|
+
for (const op of ALL_OPS) {
|
|
14
|
+
const strategy = STRATEGIES_PER_OP[op];
|
|
15
|
+
operations[op] = {
|
|
16
|
+
strategy,
|
|
17
|
+
timeoutMs: opts.timeoutMs ?? 8e3,
|
|
18
|
+
retries: failures,
|
|
19
|
+
failover: strategy === "failover",
|
|
20
|
+
...opts.weights ? { weights: opts.weights } : {}
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
return { tenants: { default: { providers, operations } } };
|
|
24
|
+
}
|
|
25
|
+
function validatePolicy(policy) {
|
|
26
|
+
const issues = [];
|
|
27
|
+
if (!policy || typeof policy !== "object") return ["policy must be an object"];
|
|
28
|
+
const root = policy;
|
|
29
|
+
if (!root.tenants || typeof root.tenants !== "object") issues.push("missing tenants map");
|
|
30
|
+
for (const [tenant, value] of Object.entries(root.tenants ?? {})) {
|
|
31
|
+
const t = value;
|
|
32
|
+
if (!Array.isArray(t.providers)) issues.push(`tenant "${tenant}" providers must be an array`);
|
|
33
|
+
if (!t.operations || typeof t.operations !== "object")
|
|
34
|
+
issues.push(`tenant "${tenant}" operations must be an object`);
|
|
35
|
+
}
|
|
36
|
+
return issues;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// src/landing.ts
|
|
40
|
+
import { readFileSync } from "fs";
|
|
41
|
+
import { fileURLToPath } from "url";
|
|
42
|
+
import { dirname, join } from "path";
|
|
43
|
+
var __dirname = dirname(fileURLToPath(import.meta.url));
|
|
44
|
+
var TEMPLATE_PATH = join(__dirname, "templates", "landing.html");
|
|
45
|
+
var STRATEGY_NAMES = ["round_robin", "load_balance", "most_fast", "failover"];
|
|
46
|
+
var VALID_STRATEGIES = new Set(STRATEGY_NAMES);
|
|
47
|
+
function validateLandingConfig(input) {
|
|
48
|
+
const issues = [];
|
|
49
|
+
if (!input || typeof input !== "object" || Array.isArray(input)) {
|
|
50
|
+
return { ok: false, issues: [{ path: "", message: "config deve ser um objeto" }] };
|
|
51
|
+
}
|
|
52
|
+
const c = input;
|
|
53
|
+
if (typeof c.product !== "string" || !c.product.trim()) {
|
|
54
|
+
issues.push({ path: "product", message: "product deve ser uma string n\xE3o vazia" });
|
|
55
|
+
}
|
|
56
|
+
if (typeof c.tagline !== "string" || !c.tagline.trim()) {
|
|
57
|
+
issues.push({ path: "tagline", message: "tagline deve ser uma string n\xE3o vazia" });
|
|
58
|
+
}
|
|
59
|
+
if (typeof c.domain !== "string" || !c.domain.trim()) {
|
|
60
|
+
issues.push({ path: "domain", message: "domain deve ser uma string n\xE3o vazia" });
|
|
61
|
+
}
|
|
62
|
+
if (!Array.isArray(c.operations) || !c.operations.every((o) => typeof o === "string" && o.trim())) {
|
|
63
|
+
issues.push({ path: "operations", message: "operations deve ser um array de strings" });
|
|
64
|
+
} else if (c.operations.length === 0) {
|
|
65
|
+
issues.push({ path: "operations", message: "operations n\xE3o pode ser vazio" });
|
|
66
|
+
}
|
|
67
|
+
if (!Array.isArray(c.strategies) || !c.strategies.every((s) => typeof s === "string")) {
|
|
68
|
+
issues.push({ path: "strategies", message: "strategies deve ser um array de strings" });
|
|
69
|
+
} else {
|
|
70
|
+
for (const s of c.strategies) {
|
|
71
|
+
if (!VALID_STRATEGIES.has(s)) {
|
|
72
|
+
issues.push({ path: "strategies", message: `estrat\xE9gia desconhecida: "${s}". V\xE1lidas: ${STRATEGY_NAMES.join(", ")}` });
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
if (c.strategies.length === 0) {
|
|
76
|
+
issues.push({ path: "strategies", message: "strategies n\xE3o pode ser vazio" });
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
if (!Array.isArray(c.providers) || !c.providers.every((p) => p && typeof p === "object" && typeof p.name === "string")) {
|
|
80
|
+
issues.push({ path: "providers", message: "providers deve ser um array de objetos { name: string }" });
|
|
81
|
+
} else {
|
|
82
|
+
c.providers.forEach((p, i) => {
|
|
83
|
+
const prov = p;
|
|
84
|
+
if (prov.weight != null && typeof prov.weight !== "number") {
|
|
85
|
+
issues.push({ path: `providers[${i}].weight`, message: "weight deve ser number" });
|
|
86
|
+
}
|
|
87
|
+
if (prov.latency != null && typeof prov.latency !== "number") {
|
|
88
|
+
issues.push({ path: `providers[${i}].latency`, message: "latency deve ser number" });
|
|
89
|
+
}
|
|
90
|
+
if (prov.health != null && (typeof prov.health !== "number" || prov.health < 0 || prov.health > 1)) {
|
|
91
|
+
issues.push({ path: `providers[${i}].health`, message: "health deve ser number em [0,1]" });
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
if (c.providers.length === 0) {
|
|
95
|
+
issues.push({ path: "providers", message: "providers n\xE3o pode ser vazio" });
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
if (c.sdkExamples != null) {
|
|
99
|
+
if (typeof c.sdkExamples !== "object" || Array.isArray(c.sdkExamples)) {
|
|
100
|
+
issues.push({ path: "sdkExamples", message: "sdkExamples deve ser um objeto { node?, python?, curl? }" });
|
|
101
|
+
} else {
|
|
102
|
+
const allowed = /* @__PURE__ */ new Set(["node", "python", "curl"]);
|
|
103
|
+
for (const k of Object.keys(c.sdkExamples)) {
|
|
104
|
+
if (!allowed.has(k)) issues.push({ path: `sdkExamples.${k}`, message: "chave de sdkExamples inv\xE1lida (use node, python ou curl)" });
|
|
105
|
+
else if (typeof c.sdkExamples[k] !== "string") {
|
|
106
|
+
issues.push({ path: `sdkExamples.${k}`, message: "sdkExamples value deve ser string" });
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
if (c.pricing != null) {
|
|
112
|
+
if (typeof c.pricing !== "object" || Array.isArray(c.pricing)) {
|
|
113
|
+
issues.push({ path: "pricing", message: "pricing deve ser um objeto de tiers por id" });
|
|
114
|
+
} else {
|
|
115
|
+
for (const [tier, v] of Object.entries(c.pricing)) {
|
|
116
|
+
if (!v || typeof v !== "object") {
|
|
117
|
+
issues.push({ path: `pricing.${tier}`, message: "tier deve ser um objeto" });
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
const t = v;
|
|
121
|
+
if (t.price != null && typeof t.price !== "string") {
|
|
122
|
+
issues.push({ path: `pricing.${tier}.price`, message: "price deve ser string" });
|
|
123
|
+
}
|
|
124
|
+
if (t.requests != null && typeof t.requests !== "string") {
|
|
125
|
+
issues.push({ path: `pricing.${tier}.requests`, message: "requests deve ser string" });
|
|
126
|
+
}
|
|
127
|
+
if (t.providers != null && typeof t.providers !== "number" && t.providers !== "ilimitado") {
|
|
128
|
+
issues.push({ path: `pricing.${tier}.providers`, message: 'providers deve ser number ou "ilimitado"' });
|
|
129
|
+
}
|
|
130
|
+
if (t.features != null && !(Array.isArray(t.features) && t.features.every((f) => typeof f === "string"))) {
|
|
131
|
+
issues.push({ path: `pricing.${tier}.features`, message: "features deve ser array de strings" });
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
if (c.cta != null) {
|
|
137
|
+
if (typeof c.cta !== "object" || Array.isArray(c.cta)) {
|
|
138
|
+
issues.push({ path: "cta", message: "cta deve ser um objeto { email? }" });
|
|
139
|
+
} else if (c.cta.email != null && typeof c.cta.email !== "string") {
|
|
140
|
+
issues.push({ path: "cta.email", message: "cta.email deve ser string" });
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
if (issues.length > 0) return { ok: false, issues };
|
|
144
|
+
return { ok: true, issues: [], config: c };
|
|
145
|
+
}
|
|
146
|
+
var DEFAULT_HERO_CODE = `import { Orchestrator } from '@layerall/sdk';
|
|
147
|
+
|
|
148
|
+
const client = new Orchestrator({
|
|
149
|
+
apiKey: process.env.LAYERALL_API_KEY!,
|
|
150
|
+
baseUrl: 'https://api.seu-allx.com',
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
const result = await client.operation('create', {
|
|
154
|
+
payload: { data: {} },
|
|
155
|
+
strategy: 'most_fast',
|
|
156
|
+
});
|
|
157
|
+
console.log(result.provider, result.status);`;
|
|
158
|
+
function defaultLandingConfig() {
|
|
159
|
+
return {
|
|
160
|
+
product: "AllX",
|
|
161
|
+
tagline: "Um SDK. N provedores. Orquestra\xE7\xE3o plug\xE1vel.",
|
|
162
|
+
domain: "orquestra\xE7\xE3o",
|
|
163
|
+
operations: ["create", "send", "status", "cancel"],
|
|
164
|
+
strategies: ["round_robin", "load_balance", "most_fast", "failover"],
|
|
165
|
+
providers: [
|
|
166
|
+
{ name: "Provider A", weight: 50, latency: 180, health: 0.96 },
|
|
167
|
+
{ name: "Provider B", weight: 30, latency: 120, health: 0.92 },
|
|
168
|
+
{ name: "Provider C", weight: 20, latency: 90, health: 0.88 }
|
|
169
|
+
],
|
|
170
|
+
sdkExamples: {
|
|
171
|
+
node: DEFAULT_HERO_CODE,
|
|
172
|
+
python: `from layerall_sdk import Orchestrator
|
|
173
|
+
client = Orchestrator(api_key=...)
|
|
174
|
+
result = client.operation('create', payload={'data': {}}, strategy='most_fast')`,
|
|
175
|
+
curl: `curl -X POST 'https://api.seu-allx.com/v1/operation/create' \\
|
|
176
|
+
-H 'Authorization: Bearer $KEY' \\
|
|
177
|
+
-d '{"payload":{"data":{}},"strategy":"most_fast"}'`
|
|
178
|
+
},
|
|
179
|
+
pricing: {
|
|
180
|
+
starter: { price: "R$ 0", requests: "10k/m\xEAs", providers: 2 },
|
|
181
|
+
pro: { price: "R$ 499", requests: "100k/m\xEAs", providers: "ilimitado" },
|
|
182
|
+
enterprise: { price: "Sob consulta", features: ["SSO", "SLA", "Suporte dedicado"] }
|
|
183
|
+
},
|
|
184
|
+
cta: { email: "contato@allx.com" }
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
function escapeHtml(s) {
|
|
188
|
+
return s.replace(/[&<>]/g, (c) => ({ "&": "&", "<": "<", ">": ">" })[c]);
|
|
189
|
+
}
|
|
190
|
+
function renderLanding(config) {
|
|
191
|
+
const template = readFileSync(TEMPLATE_PATH, "utf8");
|
|
192
|
+
const heroCode = config.sdkExamples?.node ?? DEFAULT_HERO_CODE;
|
|
193
|
+
const title = `${config.product} \u2014 ${config.tagline}`;
|
|
194
|
+
const json = JSON.stringify(config).replace(/</g, "\\u003c").replace(/>/g, "\\u003e").replace(/&/g, "\\u0026");
|
|
195
|
+
return template.replace(/__LAYERALL_TITLE__/g, escapeHtml(title)).replace(/__LAYERALL_TAGLINE__/g, escapeHtml(config.tagline)).replace(/__LAYERALL_PRODUCT__/g, escapeHtml(config.product)).replace(/__LAYERALL_DOMAIN__/g, escapeHtml(config.domain)).replace(/__LAYERALL_HERO_CODE__/g, escapeHtml(heroCode)).replace(/__LAYERALL_CONFIG_JSON__/g, json);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export {
|
|
199
|
+
buildPolicy,
|
|
200
|
+
validatePolicy,
|
|
201
|
+
validateLandingConfig,
|
|
202
|
+
defaultLandingConfig,
|
|
203
|
+
renderLanding
|
|
204
|
+
};
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
buildPolicy,
|
|
4
|
+
defaultLandingConfig,
|
|
5
|
+
renderLanding,
|
|
6
|
+
validateLandingConfig,
|
|
7
|
+
validatePolicy
|
|
8
|
+
} from "./chunk-WDABYCHT.js";
|
|
9
|
+
|
|
10
|
+
// src/cli.ts
|
|
11
|
+
import { Command } from "commander";
|
|
12
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
|
|
13
|
+
import { resolve } from "path";
|
|
14
|
+
var program = new Command();
|
|
15
|
+
program.name("layerall").description("CLI for scaffolding LayerAll policies and provider configs").version(getVersion());
|
|
16
|
+
program.command("init").description("Scaffold a default layerall.policy.json").option("-o, --out <path>", "output file", "layerall.policy.json").option("-p, --providers <ids>", "comma-separated provider ids", "providerA,providerB,providerC").option("-w, --weights <pairs>", "comma-separated id=weight pairs").action((opts) => {
|
|
17
|
+
const providers = String(opts.providers).split(",").map((s) => s.trim()).filter(Boolean);
|
|
18
|
+
const weights = parseWeights(opts.weights);
|
|
19
|
+
const policy = buildPolicy({
|
|
20
|
+
providers,
|
|
21
|
+
weights,
|
|
22
|
+
timeoutMs: 8e3,
|
|
23
|
+
retries: { max: 1, backoffMs: 300 }
|
|
24
|
+
});
|
|
25
|
+
const outPath = resolve(process.cwd(), opts.out);
|
|
26
|
+
writeFileSync(outPath, JSON.stringify(policy, null, 2) + "\n", "utf8");
|
|
27
|
+
console.log(`\u2714 Policy written to ${outPath}`);
|
|
28
|
+
});
|
|
29
|
+
program.command("validate <file>").description("Validate a layerall.policy.json file").action((file) => {
|
|
30
|
+
const path = resolve(process.cwd(), file);
|
|
31
|
+
if (!existsSync(path)) {
|
|
32
|
+
console.error(`\u2716 File not found: ${path}`);
|
|
33
|
+
process.exitCode = 1;
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
const raw = readFileSync(path, "utf8");
|
|
37
|
+
let json;
|
|
38
|
+
try {
|
|
39
|
+
json = JSON.parse(raw);
|
|
40
|
+
} catch (err) {
|
|
41
|
+
console.error(`\u2716 Invalid JSON: ${err.message}`);
|
|
42
|
+
process.exitCode = 1;
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
const issues = validatePolicy(json);
|
|
46
|
+
if (issues.length === 0) {
|
|
47
|
+
console.log("\u2714 Policy is valid.");
|
|
48
|
+
} else {
|
|
49
|
+
console.error("\u2716 Policy issues:");
|
|
50
|
+
for (const i of issues) console.error(" -", i);
|
|
51
|
+
process.exitCode = 1;
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
program.command("init-landing").description("Generate a static marketing landing page from a JSON config").option("-c, --config <path>", "path to landing-config JSON file").option("-o, --out <path>", "output directory", "landing").action((opts) => {
|
|
55
|
+
let configInput;
|
|
56
|
+
if (opts.config) {
|
|
57
|
+
const cfgPath = resolve(process.cwd(), opts.config);
|
|
58
|
+
if (!existsSync(cfgPath)) {
|
|
59
|
+
console.error(`\u2716 Config file not found: ${cfgPath}`);
|
|
60
|
+
process.exitCode = 1;
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
try {
|
|
64
|
+
configInput = JSON.parse(readFileSync(cfgPath, "utf8"));
|
|
65
|
+
} catch (err) {
|
|
66
|
+
console.error(`\u2716 Invalid JSON config: ${err.message}`);
|
|
67
|
+
process.exitCode = 1;
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
} else {
|
|
71
|
+
configInput = defaultLandingConfig();
|
|
72
|
+
}
|
|
73
|
+
const result = validateLandingConfig(configInput);
|
|
74
|
+
if (!result.ok || !result.config) {
|
|
75
|
+
console.error("\u2716 Landing config issues:");
|
|
76
|
+
for (const i of result.issues) console.error(` - ${i.path || "(root)"}: ${i.message}`);
|
|
77
|
+
process.exitCode = 1;
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
const html = renderLanding(result.config);
|
|
81
|
+
const outDir = resolve(process.cwd(), opts.out);
|
|
82
|
+
mkdirSync(outDir, { recursive: true });
|
|
83
|
+
const outPath = resolve(outDir, "index.html");
|
|
84
|
+
writeFileSync(outPath, html, "utf8");
|
|
85
|
+
console.log(`\u2714 Landing written to ${outPath}`);
|
|
86
|
+
});
|
|
87
|
+
program.parse(process.argv);
|
|
88
|
+
function parseWeights(input) {
|
|
89
|
+
if (!input) return void 0;
|
|
90
|
+
const out = {};
|
|
91
|
+
for (const pair of String(input).split(",")) {
|
|
92
|
+
const [id, w] = pair.split("=");
|
|
93
|
+
if (id) out[id.trim()] = Number(w ?? 1);
|
|
94
|
+
}
|
|
95
|
+
return out;
|
|
96
|
+
}
|
|
97
|
+
function getVersion() {
|
|
98
|
+
try {
|
|
99
|
+
const pkg = JSON.parse(
|
|
100
|
+
readFileSync(new URL("../package.json", import.meta.url), "utf8")
|
|
101
|
+
);
|
|
102
|
+
return pkg.version ?? "0.0.0";
|
|
103
|
+
} catch {
|
|
104
|
+
return "0.0.0";
|
|
105
|
+
}
|
|
106
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { PolicyDocument } from '@layerall/core';
|
|
2
|
+
|
|
3
|
+
/** Builds a sensible default policy document for a set of provider ids. */
|
|
4
|
+
declare function buildPolicy(opts: {
|
|
5
|
+
providers: string[];
|
|
6
|
+
weights?: Record<string, number>;
|
|
7
|
+
timeoutMs?: number;
|
|
8
|
+
retries?: {
|
|
9
|
+
max: number;
|
|
10
|
+
backoffMs: number;
|
|
11
|
+
};
|
|
12
|
+
}): PolicyDocument;
|
|
13
|
+
/** Validates a policy document shape; returns a list of human-readable issues. */
|
|
14
|
+
declare function validatePolicy(policy: unknown): string[];
|
|
15
|
+
|
|
16
|
+
declare const STRATEGY_NAMES: readonly ["round_robin", "load_balance", "most_fast", "failover"];
|
|
17
|
+
/** Public landing page config — drives every section of the template. */
|
|
18
|
+
interface LandingConfig {
|
|
19
|
+
/** Product / brand name (e.g. "AllGeo"). */
|
|
20
|
+
product: string;
|
|
21
|
+
/** One-line tagline used in `<title>`, meta description and the hero gradient line. */
|
|
22
|
+
tagline: string;
|
|
23
|
+
/** Word describing the orchestration domain (e.g. "geocode reverso"). */
|
|
24
|
+
domain: string;
|
|
25
|
+
/** Operation names the orchestrator exposes (e.g. ["reverse","batch"]). */
|
|
26
|
+
operations: string[];
|
|
27
|
+
/** Strategy names enabled on this product (subset of LayerAll strategies). */
|
|
28
|
+
strategies: (typeof STRATEGY_NAMES)[number][];
|
|
29
|
+
/** One entry per integrated provider. */
|
|
30
|
+
providers: LandingProvider[];
|
|
31
|
+
/** Source snippets shown in the hero and SDK tabs, keyed by language id. */
|
|
32
|
+
sdkExamples?: Partial<Record<'node' | 'python' | 'curl', string>>;
|
|
33
|
+
/** Pricing tiers keyed by tier id (e.g. "starter" / "pro" / "enterprise"). */
|
|
34
|
+
pricing?: Record<string, LandingPricingTier>;
|
|
35
|
+
/** Contact call-to-action. */
|
|
36
|
+
cta?: {
|
|
37
|
+
/** Contact email used by the primary CTA buttons. */
|
|
38
|
+
email?: string;
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
interface LandingProvider {
|
|
42
|
+
/** Display name (e.g. "Google Maps"). */
|
|
43
|
+
name: string;
|
|
44
|
+
/** Traffic weight used by load_balance (defaults to 1). */
|
|
45
|
+
weight?: number;
|
|
46
|
+
/** Base latency in ms shown in the provider card. */
|
|
47
|
+
latency?: number;
|
|
48
|
+
/** Health score in [0,1] shown in the provider card. */
|
|
49
|
+
health?: number;
|
|
50
|
+
}
|
|
51
|
+
interface LandingPricingTier {
|
|
52
|
+
/** Display price (e.g. "R$ 0", "Sob consulta"). */
|
|
53
|
+
price?: string;
|
|
54
|
+
/** Volume legend (e.g. "10k/mês"). */
|
|
55
|
+
requests?: string;
|
|
56
|
+
/** Count of providers allowed, or "ilimitado". */
|
|
57
|
+
providers?: number | 'ilimitado';
|
|
58
|
+
/** Extra bullet features. */
|
|
59
|
+
features?: string[];
|
|
60
|
+
}
|
|
61
|
+
interface LandingValidationIssue {
|
|
62
|
+
path: string;
|
|
63
|
+
message: string;
|
|
64
|
+
}
|
|
65
|
+
interface LandingValidationResult {
|
|
66
|
+
ok: boolean;
|
|
67
|
+
issues: LandingValidationIssue[];
|
|
68
|
+
config?: LandingConfig;
|
|
69
|
+
}
|
|
70
|
+
/** Validates a raw config object against the LandingConfig shape. */
|
|
71
|
+
declare function validateLandingConfig(input: unknown): LandingValidationResult;
|
|
72
|
+
/** Default config used when --config is not provided: a brandable agnostic template. */
|
|
73
|
+
declare function defaultLandingConfig(): LandingConfig;
|
|
74
|
+
/** Renders the full single-file landing HTML for the supplied config. */
|
|
75
|
+
declare function renderLanding(config: LandingConfig): string;
|
|
76
|
+
|
|
77
|
+
export { type LandingConfig, type LandingPricingTier, type LandingProvider, type LandingValidationIssue, type LandingValidationResult, buildPolicy, defaultLandingConfig, renderLanding, validateLandingConfig, validatePolicy };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import {
|
|
2
|
+
buildPolicy,
|
|
3
|
+
defaultLandingConfig,
|
|
4
|
+
renderLanding,
|
|
5
|
+
validateLandingConfig,
|
|
6
|
+
validatePolicy
|
|
7
|
+
} from "./chunk-WDABYCHT.js";
|
|
8
|
+
export {
|
|
9
|
+
buildPolicy,
|
|
10
|
+
defaultLandingConfig,
|
|
11
|
+
renderLanding,
|
|
12
|
+
validateLandingConfig,
|
|
13
|
+
validatePolicy
|
|
14
|
+
};
|