@mesh-tech/mesh-cli 0.21.0 → 0.22.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/dist/bin/mesh.js +229 -2
- package/dist/bin/mesh.js.map +1 -1
- package/dist/build-info.json +2 -2
- package/dist/src/commands/app-check.d.ts +16 -1
- package/dist/src/commands/app-check.js +260 -0
- package/dist/src/commands/temporal.js +5 -1
- package/dist/src/commands/temporal.js.map +1 -1
- package/fragments/agents/AGENTS.md.hbs +13 -0
- package/fragments/base/README.md.hbs +9 -0
- package/fragments/base/index.ts.hbs +27 -0
- package/fragments/service/api/openapi.json.hbs +42 -0
- package/fragments/service/docs/pages/getting-started.md.hbs +15 -0
- package/package.json +2 -2
- package/skills/core/SKILL.md +1 -1
package/dist/bin/mesh.js
CHANGED
|
@@ -6300,7 +6300,11 @@ var init_src = __esm({
|
|
|
6300
6300
|
description: z.string().optional(),
|
|
6301
6301
|
surfaces: apiSurfaceSchema,
|
|
6302
6302
|
credentials: z.object({ keyedBy: z.string().nullish() }).optional(),
|
|
6303
|
-
docs: z.object({
|
|
6303
|
+
docs: z.object({
|
|
6304
|
+
url: z.string().min(1),
|
|
6305
|
+
contentHash: z.string().min(1),
|
|
6306
|
+
siteUrl: z.string().min(1).optional()
|
|
6307
|
+
}),
|
|
6304
6308
|
enabledOps: z.array(z.string()),
|
|
6305
6309
|
// The consumer schema strips unknown keys, so a producer-side field that is
|
|
6306
6310
|
// not mirrored here never reaches a consumer — mirror every addition.
|
|
@@ -10793,6 +10797,209 @@ function authzSurface(app) {
|
|
|
10793
10797
|
walk2(app);
|
|
10794
10798
|
return hits;
|
|
10795
10799
|
}
|
|
10800
|
+
function balancedSpan(text, open) {
|
|
10801
|
+
const closers = { "(": ")", "{": "}", "[": "]" };
|
|
10802
|
+
const stack = [];
|
|
10803
|
+
let quote;
|
|
10804
|
+
for (let i = open; i < text.length; i++) {
|
|
10805
|
+
const ch = text[i];
|
|
10806
|
+
if (quote) {
|
|
10807
|
+
if (ch === "\\") i++;
|
|
10808
|
+
else if (ch === quote) quote = void 0;
|
|
10809
|
+
continue;
|
|
10810
|
+
}
|
|
10811
|
+
if (ch === '"' || ch === "'" || ch === "`") {
|
|
10812
|
+
quote = ch;
|
|
10813
|
+
continue;
|
|
10814
|
+
}
|
|
10815
|
+
if (ch === "/" && text[i + 1] === "/") {
|
|
10816
|
+
const eol = text.indexOf("\n", i);
|
|
10817
|
+
if (eol === -1) return "";
|
|
10818
|
+
i = eol;
|
|
10819
|
+
continue;
|
|
10820
|
+
}
|
|
10821
|
+
if (ch === "/" && text[i + 1] === "*") {
|
|
10822
|
+
const end = text.indexOf("*/", i + 2);
|
|
10823
|
+
if (end === -1) return "";
|
|
10824
|
+
i = end + 1;
|
|
10825
|
+
continue;
|
|
10826
|
+
}
|
|
10827
|
+
if (closers[ch]) stack.push(closers[ch]);
|
|
10828
|
+
else if (ch === ")" || ch === "}" || ch === "]") {
|
|
10829
|
+
if (stack.pop() !== ch) return "";
|
|
10830
|
+
if (stack.length === 0) return text.slice(open, i + 1);
|
|
10831
|
+
}
|
|
10832
|
+
}
|
|
10833
|
+
return "";
|
|
10834
|
+
}
|
|
10835
|
+
function splitTopLevel(inner) {
|
|
10836
|
+
const parts = [];
|
|
10837
|
+
let depth = 0;
|
|
10838
|
+
let quote;
|
|
10839
|
+
let start = 0;
|
|
10840
|
+
for (let i = 0; i < inner.length; i++) {
|
|
10841
|
+
const ch = inner[i];
|
|
10842
|
+
if (quote) {
|
|
10843
|
+
if (ch === "\\") i++;
|
|
10844
|
+
else if (ch === quote) quote = void 0;
|
|
10845
|
+
continue;
|
|
10846
|
+
}
|
|
10847
|
+
if (ch === '"' || ch === "'" || ch === "`") {
|
|
10848
|
+
quote = ch;
|
|
10849
|
+
continue;
|
|
10850
|
+
}
|
|
10851
|
+
if (ch === "/" && inner[i + 1] === "/") {
|
|
10852
|
+
const eol = inner.indexOf("\n", i);
|
|
10853
|
+
if (eol === -1) break;
|
|
10854
|
+
i = eol;
|
|
10855
|
+
continue;
|
|
10856
|
+
}
|
|
10857
|
+
if (ch === "/" && inner[i + 1] === "*") {
|
|
10858
|
+
const end = inner.indexOf("*/", i + 2);
|
|
10859
|
+
if (end === -1) break;
|
|
10860
|
+
i = end + 1;
|
|
10861
|
+
continue;
|
|
10862
|
+
}
|
|
10863
|
+
if (ch === "(" || ch === "{" || ch === "[") depth++;
|
|
10864
|
+
else if (ch === ")" || ch === "}" || ch === "]") depth--;
|
|
10865
|
+
else if (ch === "," && depth === 0) {
|
|
10866
|
+
parts.push(inner.slice(start, i));
|
|
10867
|
+
start = i + 1;
|
|
10868
|
+
}
|
|
10869
|
+
}
|
|
10870
|
+
const last = inner.slice(start);
|
|
10871
|
+
if (last.trim()) parts.push(last);
|
|
10872
|
+
return parts;
|
|
10873
|
+
}
|
|
10874
|
+
function stripLeadingComments(entry) {
|
|
10875
|
+
let text = entry;
|
|
10876
|
+
for (; ; ) {
|
|
10877
|
+
const next = text.replace(/^\s*(?:\/\/[^\n]*(?:\n|$)|\/\*[\s\S]*?\*\/)/, "");
|
|
10878
|
+
if (next === text) return text;
|
|
10879
|
+
text = next;
|
|
10880
|
+
}
|
|
10881
|
+
}
|
|
10882
|
+
function stripTrailingComments(value) {
|
|
10883
|
+
let out = "";
|
|
10884
|
+
let depth = 0;
|
|
10885
|
+
let quote;
|
|
10886
|
+
for (let i = 0; i < value.length; i++) {
|
|
10887
|
+
const ch = value[i];
|
|
10888
|
+
if (quote) {
|
|
10889
|
+
out += ch;
|
|
10890
|
+
if (ch === "\\") {
|
|
10891
|
+
out += value[i + 1] ?? "";
|
|
10892
|
+
i++;
|
|
10893
|
+
} else if (ch === quote) quote = void 0;
|
|
10894
|
+
continue;
|
|
10895
|
+
}
|
|
10896
|
+
if (ch === '"' || ch === "'" || ch === "`") {
|
|
10897
|
+
quote = ch;
|
|
10898
|
+
out += ch;
|
|
10899
|
+
continue;
|
|
10900
|
+
}
|
|
10901
|
+
if (depth === 0 && ch === "/" && value[i + 1] === "/") {
|
|
10902
|
+
const eol = value.indexOf("\n", i);
|
|
10903
|
+
if (eol === -1) break;
|
|
10904
|
+
i = eol;
|
|
10905
|
+
out += "\n";
|
|
10906
|
+
continue;
|
|
10907
|
+
}
|
|
10908
|
+
if (depth === 0 && ch === "/" && value[i + 1] === "*") {
|
|
10909
|
+
const end = value.indexOf("*/", i + 2);
|
|
10910
|
+
if (end === -1) break;
|
|
10911
|
+
i = end + 1;
|
|
10912
|
+
continue;
|
|
10913
|
+
}
|
|
10914
|
+
if (ch === "(" || ch === "{" || ch === "[") depth++;
|
|
10915
|
+
else if (ch === ")" || ch === "}" || ch === "]") depth--;
|
|
10916
|
+
out += ch;
|
|
10917
|
+
}
|
|
10918
|
+
return out.trim();
|
|
10919
|
+
}
|
|
10920
|
+
function topLevelProperties(objectText) {
|
|
10921
|
+
const props = /* @__PURE__ */ new Map();
|
|
10922
|
+
if (!objectText.startsWith("{") || !objectText.endsWith("}")) return props;
|
|
10923
|
+
for (const entry of splitTopLevel(objectText.slice(1, -1))) {
|
|
10924
|
+
const text = stripLeadingComments(entry).trim();
|
|
10925
|
+
if (!text || text.startsWith("...")) continue;
|
|
10926
|
+
const m = RE_PROPERTY.exec(text);
|
|
10927
|
+
if (!m) continue;
|
|
10928
|
+
const key = m[1] ?? m[2] ?? m[3];
|
|
10929
|
+
props.set(key, stripTrailingComments(m[4] ?? key));
|
|
10930
|
+
}
|
|
10931
|
+
return props;
|
|
10932
|
+
}
|
|
10933
|
+
function objectLiteralOf(value, program2) {
|
|
10934
|
+
if (!value) return void 0;
|
|
10935
|
+
if (value.startsWith("{")) return balancedSpan(value, 0) || void 0;
|
|
10936
|
+
const id = /^[A-Za-z_$][\w$]*$/.exec(value)?.[0];
|
|
10937
|
+
if (!id) return void 0;
|
|
10938
|
+
const decl = new RegExp(`\\bconst\\s+${id}\\s*(?::[^=]+)?=\\s*\\{`).exec(program2);
|
|
10939
|
+
return decl ? balancedSpan(program2, decl.index + decl[0].length - 1) || void 0 : void 0;
|
|
10940
|
+
}
|
|
10941
|
+
function optionsObject(body) {
|
|
10942
|
+
for (const arg of splitTopLevel(body.slice(1, -1))) {
|
|
10943
|
+
const trimmed = arg.trim();
|
|
10944
|
+
if (trimmed.startsWith("{")) return balancedSpan(trimmed, 0) || void 0;
|
|
10945
|
+
}
|
|
10946
|
+
return void 0;
|
|
10947
|
+
}
|
|
10948
|
+
function surfaceDeclarations(text, pattern = RE_SURFACE_NEW) {
|
|
10949
|
+
const out = [];
|
|
10950
|
+
for (const m of text.matchAll(pattern)) {
|
|
10951
|
+
const body = balancedSpan(text, m.index + m[0].length - 1);
|
|
10952
|
+
if (!body) continue;
|
|
10953
|
+
out.push({ ctor: m[1] ?? "ApiDocs", body, line: text.slice(0, m.index).split("\n").length });
|
|
10954
|
+
}
|
|
10955
|
+
return out;
|
|
10956
|
+
}
|
|
10957
|
+
function isClientAuth(value) {
|
|
10958
|
+
return value !== void 0 && value !== "undefined" && !RE_DEV_BYPASS.test(value);
|
|
10959
|
+
}
|
|
10960
|
+
function docsSiteFindings(text) {
|
|
10961
|
+
const findings = [];
|
|
10962
|
+
for (const decl of surfaceDeclarations(text)) {
|
|
10963
|
+
const options = optionsObject(decl.body);
|
|
10964
|
+
if (!options) continue;
|
|
10965
|
+
const args = topLevelProperties(options);
|
|
10966
|
+
const docsText = objectLiteralOf(args.get("docs"), text);
|
|
10967
|
+
if (!docsText) continue;
|
|
10968
|
+
const docs = topLevelProperties(docsText);
|
|
10969
|
+
if (docs.get("site") === "false") continue;
|
|
10970
|
+
if (docs.get("site") !== "true" && !DOCS_SITE_FIELDS.some((f) => docs.has(f))) continue;
|
|
10971
|
+
const httpText = objectLiteralOf(topLevelProperties(objectLiteralOf(args.get("surfaces"), text) ?? "{}").get("http"), text);
|
|
10972
|
+
const hasClient = isClientAuth(docs.get("auth")) || isClientAuth(topLevelProperties(httpText ?? "{}").get("auth"));
|
|
10973
|
+
if (!hasClient) {
|
|
10974
|
+
findings.push({
|
|
10975
|
+
code: "DOCS_SITE_UNAUTHED",
|
|
10976
|
+
line: decl.line,
|
|
10977
|
+
why: `mesh.apps.${decl.ctor} declares a docs site with no OIDC client \u2014 the site is served behind the platform sign-in proxy, which needs one (docs.auth, or a client on surfaces.http.auth)`
|
|
10978
|
+
});
|
|
10979
|
+
}
|
|
10980
|
+
const kindStated = decl.ctor === "AppApiSurface" || decl.ctor === "VendorApiSurface" || args.has("kind");
|
|
10981
|
+
if (!kindStated && !docs.has("audience")) {
|
|
10982
|
+
findings.push({
|
|
10983
|
+
code: "DOCS_AUDIENCE_IMPLICIT",
|
|
10984
|
+
line: decl.line,
|
|
10985
|
+
why: `mesh.apps.${decl.ctor} declares a docs site but neither its kind nor its audience \u2014 who may read it would fall out of the name heuristic, and a vendor surface must never default to partner-readable that way`
|
|
10986
|
+
});
|
|
10987
|
+
}
|
|
10988
|
+
}
|
|
10989
|
+
for (const decl of surfaceDeclarations(text, RE_APIDOCS_NEW)) {
|
|
10990
|
+
const options = optionsObject(decl.body);
|
|
10991
|
+
if (!options) continue;
|
|
10992
|
+
const args = topLevelProperties(options);
|
|
10993
|
+
if (!args.has("ingress") || args.get("ingress") === "undefined") continue;
|
|
10994
|
+
if (isClientAuth(args.get("auth"))) continue;
|
|
10995
|
+
findings.push({
|
|
10996
|
+
code: "DOCS_SITE_UNAUTHED",
|
|
10997
|
+
line: decl.line,
|
|
10998
|
+
why: `mesh.apps.ApiDocs is published on an ingress with no auth \u2014 the site (reference and playground) is reachable by anyone; put the platform sign-in proxy in front of it, or drop the ingress`
|
|
10999
|
+
});
|
|
11000
|
+
}
|
|
11001
|
+
return findings;
|
|
11002
|
+
}
|
|
10796
11003
|
function listApps(root) {
|
|
10797
11004
|
const apps = [];
|
|
10798
11005
|
const appsDir = path22.join(root, "apps");
|
|
@@ -10841,6 +11048,7 @@ function scanApp(root, appRel) {
|
|
|
10841
11048
|
if (!/env\.register\(|\.annotate\(/.test(readText(index))) {
|
|
10842
11049
|
hit("NO_REGISTER", indexRel, "never calls env.register({\u2026}) \u2014 the Hub cannot list this app");
|
|
10843
11050
|
}
|
|
11051
|
+
for (const f of docsSiteFindings(readText(index))) hit(f.code, `${indexRel}:${f.line}`, f.why);
|
|
10844
11052
|
for (const svc of serviceDirs(app)) {
|
|
10845
11053
|
for (const f of srcFiles(svc)) {
|
|
10846
11054
|
const text = readText(f);
|
|
@@ -10997,7 +11205,7 @@ function registerAppCommands(program2) {
|
|
|
10997
11205
|
}
|
|
10998
11206
|
});
|
|
10999
11207
|
}
|
|
11000
|
-
var APP_CHECK_RULES, APP_CHECK_CODES, SRC_EXT, SKIP_DIRS, NON_SERVICE_DIRS, RE_HTML, RE_OIDC, RE_COOKIE, RE_LOGGER, RE_EXPOSURE, RE_POINTER, RE_METADATA, RE_IAC_GRANT, RE_POINTER_OVERRIDE, RE_PASSWORD, RE_ALLOWLIST, RE_USERS_TABLE;
|
|
11208
|
+
var APP_CHECK_RULES, APP_CHECK_CODES, SRC_EXT, SKIP_DIRS, NON_SERVICE_DIRS, RE_HTML, RE_OIDC, RE_COOKIE, RE_LOGGER, RE_EXPOSURE, RE_POINTER, RE_METADATA, RE_IAC_GRANT, RE_POINTER_OVERRIDE, RE_PASSWORD, RE_ALLOWLIST, RE_USERS_TABLE, RE_SURFACE_NEW, RE_APIDOCS_NEW, DOCS_SITE_FIELDS, RE_DEV_BYPASS, RE_PROPERTY;
|
|
11001
11209
|
var init_app_check = __esm({
|
|
11002
11210
|
"libs/mesh-cli/src/commands/app-check.ts"() {
|
|
11003
11211
|
"use strict";
|
|
@@ -11096,6 +11304,20 @@ var init_app_check = __esm({
|
|
|
11096
11304
|
severity: "block",
|
|
11097
11305
|
remediation: "drop the table; people and roles live in Zitadel and are administered from the Hub",
|
|
11098
11306
|
scope: "app"
|
|
11307
|
+
},
|
|
11308
|
+
DOCS_SITE_UNAUTHED: {
|
|
11309
|
+
gate: "0.3",
|
|
11310
|
+
title: "Docs site behind the platform sign-in",
|
|
11311
|
+
severity: "block",
|
|
11312
|
+
remediation: 'on an API surface: docs: { auth: { clientId, clientSecret } } (or a client on surfaces.http.auth), or opt out with docs: { site: false }; on a mesh.apps.ApiDocs: auth: { provider: "mesh", clientId, clientSecret }, or drop the ingress (an in-cluster site is reachable through mesh dev)',
|
|
11313
|
+
scope: "app"
|
|
11314
|
+
},
|
|
11315
|
+
DOCS_AUDIENCE_IMPLICIT: {
|
|
11316
|
+
gate: "H",
|
|
11317
|
+
title: "Docs audience stated, not derived",
|
|
11318
|
+
severity: "block",
|
|
11319
|
+
remediation: 'declare the surface as mesh.apps.AppApiSurface or VendorApiSurface (or pass kind: "app" | "vendor"), or state docs: { audience: { roles: [\u2026] } }',
|
|
11320
|
+
scope: "app"
|
|
11099
11321
|
}
|
|
11100
11322
|
};
|
|
11101
11323
|
APP_CHECK_CODES = Object.keys(APP_CHECK_RULES);
|
|
@@ -11127,6 +11349,11 @@ var init_app_check = __esm({
|
|
|
11127
11349
|
RE_PASSWORD = /(^|[^a-z])(bcrypt|argon2)|password_hash|passwordHash|hashPassword|temporaryPassword.*(send|mail)|(send|mail).*temporaryPassword/;
|
|
11128
11350
|
RE_ALLOWLIST = /ADMIN_EMAILS|ALLOWED_EMAILS|allowedEmails|adminEmails/;
|
|
11129
11351
|
RE_USERS_TABLE = /create table (if not exists )?"?(users|roles|user_roles|permissions)"?/i;
|
|
11352
|
+
RE_SURFACE_NEW = /new\s+mesh\.apps\.(ApiSurface|AppApiSurface|VendorApiSurface|Integration)\s*\(/g;
|
|
11353
|
+
RE_APIDOCS_NEW = /new\s+mesh\.apps\.ApiDocs\s*\(/g;
|
|
11354
|
+
DOCS_SITE_FIELDS = ["pages", "playground", "audience", "basePath", "ingress", "auth"];
|
|
11355
|
+
RE_DEV_BYPASS = /^["']dev-bypass["']$/;
|
|
11356
|
+
RE_PROPERTY = /^(?:"([^"]+)"|'([^']+)'|([A-Za-z_$][\w$]*))\s*(?::\s*([\s\S]*))?$/;
|
|
11130
11357
|
}
|
|
11131
11358
|
});
|
|
11132
11359
|
|