@mesh-tech/mesh-cli 0.32.0 → 0.33.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 +91 -15
- package/dist/bin/mesh.js.map +1 -1
- package/dist/build-info.json +2 -2
- package/dist/src/commands/app-check.d.ts +6 -1
- package/dist/src/commands/app-check.js +70 -12
- package/dist/src/commands/create-app.d.ts +3 -0
- package/dist/src/commands/create-app.js +25 -0
- package/dist/src/commands/temporal.js +1 -1
- package/dist/src/commands/temporal.js.map +1 -1
- package/dist/src/utils/scaffold-versions.d.ts +2 -0
- package/dist/src/utils/scaffold-versions.js +2 -0
- package/fragments/base/index.ts.hbs +104 -0
- package/fragments/base/package.json.hbs +10 -0
- package/fragments/events/api/src/events.ts.hbs +161 -0
- package/fragments/events/schema.ts.hbs +23 -0
- package/fragments/service/api/src/index.ts.hbs +27 -0
- package/package.json +2 -2
package/dist/bin/mesh.js
CHANGED
|
@@ -6912,7 +6912,7 @@ var init_src = __esm({
|
|
|
6912
6912
|
z.object({ op: z.string().trim().min(1) }),
|
|
6913
6913
|
z.object({ unavailable: z.string().trim().min(1) })
|
|
6914
6914
|
]);
|
|
6915
|
-
apiSurfaceKindSchema = z.enum(["app", "vendor"]);
|
|
6915
|
+
apiSurfaceKindSchema = z.enum(["app", "vendor", "events"]);
|
|
6916
6916
|
apiDocsSiteAudienceSchema = z.discriminatedUnion("readers", [
|
|
6917
6917
|
z.object({ readers: z.literal("partner"), roles: z.array(z.string().min(1)) }),
|
|
6918
6918
|
z.object({ readers: z.literal("operator") })
|
|
@@ -11846,6 +11846,53 @@ function statedKind(decl, args) {
|
|
|
11846
11846
|
if (decl.ctor === "VendorApiSurface") return "vendor";
|
|
11847
11847
|
return /^["']([a-z]+)["']$/.exec(args.get("kind") ?? "")?.[1];
|
|
11848
11848
|
}
|
|
11849
|
+
function programIdentities(text) {
|
|
11850
|
+
const identities = [];
|
|
11851
|
+
for (const d of surfaceDeclarations(text, RE_IDENTITY_NEW)) {
|
|
11852
|
+
const options = optionsObject(d.body, text);
|
|
11853
|
+
if (!options) continue;
|
|
11854
|
+
const binding = [...text.matchAll(RE_IDENTITY_BINDING)].find(
|
|
11855
|
+
(m) => text.indexOf(d.body, m.index) === m.index + m[0].length - 1
|
|
11856
|
+
)?.[1];
|
|
11857
|
+
identities.push({ binding, options: topLevelProperties(options) });
|
|
11858
|
+
}
|
|
11859
|
+
return identities;
|
|
11860
|
+
}
|
|
11861
|
+
function declaredProjectRoles(identity) {
|
|
11862
|
+
const rolesText = identity.options.get("roles");
|
|
11863
|
+
const coarseText = identity.options.get("coarseRoles");
|
|
11864
|
+
if (rolesText !== void 0) return stringList(rolesText);
|
|
11865
|
+
if (coarseText !== void 0) return stringList(coarseText);
|
|
11866
|
+
return [];
|
|
11867
|
+
}
|
|
11868
|
+
function eventsRoleFindings(text) {
|
|
11869
|
+
const findings = [];
|
|
11870
|
+
const identities = programIdentities(text);
|
|
11871
|
+
for (const decl of surfaceDeclarations(text, RE_EVENTS_NEW)) {
|
|
11872
|
+
const options = optionsObject(decl.body, text);
|
|
11873
|
+
if (!options) continue;
|
|
11874
|
+
const args = topLevelProperties(options);
|
|
11875
|
+
if (args.get("enabled") === "false") continue;
|
|
11876
|
+
if (identities.length === 0) {
|
|
11877
|
+
continue;
|
|
11878
|
+
}
|
|
11879
|
+
const identityText = objectLiteralOf(args.get("identity"), text);
|
|
11880
|
+
const projectExpr = identityText ? topLevelProperties(identityText).get("projectId") : void 0;
|
|
11881
|
+
const projectRoot = projectExpr ? /^([A-Za-z_$][\w$]*)\.projectId\b/.exec(projectExpr.trim())?.[1] : void 0;
|
|
11882
|
+
const identity = (projectRoot ? identities.find((id) => id.binding === projectRoot) : void 0) ?? (identities.length === 1 ? identities[0] : void 0);
|
|
11883
|
+
if (!identity) continue;
|
|
11884
|
+
const declared = declaredProjectRoles(identity);
|
|
11885
|
+
if (!declared) continue;
|
|
11886
|
+
const missing = EVENTS_PROJECT_ROLES.filter((r) => !declared.includes(r));
|
|
11887
|
+
if (missing.length === 0) continue;
|
|
11888
|
+
findings.push({
|
|
11889
|
+
code: "EVENTS_ROLES_UNDECLARED",
|
|
11890
|
+
line: decl.line,
|
|
11891
|
+
why: `mesh.apps.EventsService is declared but the app's ZitadelAppIdentity does not declare ${missing.map((r) => `"${r}"`).join(" or ")} \u2014 the events service authorizes bearer callers by these project roles, and a role the identity never declares cannot be granted from the Hub's Access tab, so no subscriber can be admitted`
|
|
11892
|
+
});
|
|
11893
|
+
}
|
|
11894
|
+
return findings;
|
|
11895
|
+
}
|
|
11849
11896
|
function docsAudienceUngrantable(text, decl, args, docs, httpText) {
|
|
11850
11897
|
const findings = [];
|
|
11851
11898
|
let roles;
|
|
@@ -11856,22 +11903,14 @@ function docsAudienceUngrantable(text, decl, args, docs, httpText) {
|
|
|
11856
11903
|
roles = DEFAULT_PARTNER_DOCS_ROLES;
|
|
11857
11904
|
}
|
|
11858
11905
|
if (!roles || roles.length === 0) return findings;
|
|
11859
|
-
const identities =
|
|
11860
|
-
for (const d of surfaceDeclarations(text, RE_IDENTITY_NEW)) {
|
|
11861
|
-
const options = optionsObject(d.body, text);
|
|
11862
|
-
if (!options) continue;
|
|
11863
|
-
const binding = [...text.matchAll(RE_IDENTITY_BINDING)].find((m) => text.indexOf(d.body, m.index) === m.index + m[0].length - 1)?.[1];
|
|
11864
|
-
identities.push({ binding, options: topLevelProperties(options) });
|
|
11865
|
-
}
|
|
11906
|
+
const identities = programIdentities(text);
|
|
11866
11907
|
if (identities.length === 0) return findings;
|
|
11867
11908
|
const docsAuth = objectLiteralOf(docs.get("auth"), text);
|
|
11868
11909
|
const clientExpr = (docsAuth ? topLevelProperties(docsAuth).get("clientId") : void 0) ?? (httpText ? topLevelProperties(objectLiteralOf(topLevelProperties(httpText).get("auth"), text) ?? "{}").get("clientId") : void 0);
|
|
11869
11910
|
const clientRoot = clientExpr ? /^([A-Za-z_$][\w$]*)\.(?:clientIds|applications)\./.exec(clientExpr.trim())?.[1] : void 0;
|
|
11870
11911
|
const identity = (clientRoot ? identities.find((id) => id.binding === clientRoot) : void 0) ?? (identities.length === 1 ? identities[0] : void 0);
|
|
11871
11912
|
if (!identity) return findings;
|
|
11872
|
-
const
|
|
11873
|
-
const coarseText = identity.options.get("coarseRoles");
|
|
11874
|
-
const declared = rolesText !== void 0 ? stringList(rolesText) : coarseText !== void 0 ? stringList(coarseText) : [];
|
|
11913
|
+
const declared = declaredProjectRoles(identity);
|
|
11875
11914
|
if (declared) {
|
|
11876
11915
|
for (const role of roles) {
|
|
11877
11916
|
if (declared.includes(role)) continue;
|
|
@@ -11942,6 +11981,7 @@ function scanApp(root, appRel, resolution = probeResolution(root, appRel)) {
|
|
|
11942
11981
|
hit("NO_REGISTER", indexRel, "never calls env.register({\u2026}) \u2014 the Hub cannot list this app");
|
|
11943
11982
|
}
|
|
11944
11983
|
for (const f of docsSiteFindings(readText(index))) hit(f.code, `${indexRel}:${f.line}`, f.why);
|
|
11984
|
+
for (const f of eventsRoleFindings(readText(index))) hit(f.code, `${indexRel}:${f.line}`, f.why);
|
|
11945
11985
|
for (const svc of serviceDirs(app)) {
|
|
11946
11986
|
for (const f of srcFiles(svc)) {
|
|
11947
11987
|
const text = readText(f);
|
|
@@ -12103,7 +12143,7 @@ function registerAppCommands(program2) {
|
|
|
12103
12143
|
}
|
|
12104
12144
|
});
|
|
12105
12145
|
}
|
|
12106
|
-
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, RE_IDENTITY_NEW, RE_IDENTITY_BINDING, DEFAULT_PARTNER_DOCS_ROLES, DOCS_SITE_FIELDS, DOCS_SITE_RETIRED_FIELDS, RE_PROPERTY;
|
|
12146
|
+
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, RE_IDENTITY_NEW, RE_EVENTS_NEW, EVENTS_PROJECT_ROLES, RE_IDENTITY_BINDING, DEFAULT_PARTNER_DOCS_ROLES, DOCS_SITE_FIELDS, DOCS_SITE_RETIRED_FIELDS, RE_PROPERTY;
|
|
12107
12147
|
var init_app_check = __esm({
|
|
12108
12148
|
"libs/mesh-cli/src/commands/app-check.ts"() {
|
|
12109
12149
|
"use strict";
|
|
@@ -12242,6 +12282,13 @@ var init_app_check = __esm({
|
|
|
12242
12282
|
remediation: `declare every audience role on the ZitadelAppIdentity (roles: ["docs"]) so the Hub can grant it from the app's Access tab \u2014 the audience is published on the surface's registry entry as the reader set for the Hub to admit by, and a role nobody can be granted admits nobody`,
|
|
12243
12283
|
scope: "app"
|
|
12244
12284
|
},
|
|
12285
|
+
EVENTS_ROLES_UNDECLARED: {
|
|
12286
|
+
gate: "H",
|
|
12287
|
+
title: "Events roles grantable from the Hub",
|
|
12288
|
+
severity: "block",
|
|
12289
|
+
remediation: "spread mesh.apps.EVENTS_PROJECT_ROLES into the ZitadelAppIdentity's roles (roles: [...schemaDef.coarseRoles, ...mesh.apps.EVENTS_PROJECT_ROLES]) so events:subscriber and events:admin are project roles the Hub's Access tab can grant \u2014 the primitive cannot add them, because the identity builds its roles before the events service exists and a role added afterwards is absent from the coarseRoles the Hub reads",
|
|
12290
|
+
scope: "app"
|
|
12291
|
+
},
|
|
12245
12292
|
DOCS_VENDOR_AUDIENCE_PARTNER: {
|
|
12246
12293
|
gate: "H",
|
|
12247
12294
|
title: "Vendor reference never partner-readable",
|
|
@@ -12273,7 +12320,7 @@ var init_app_check = __esm({
|
|
|
12273
12320
|
RE_OIDC = /openid-client|code_verifier|authorization_code|buildAuthorizeUrl|end_session/;
|
|
12274
12321
|
RE_COOKIE = /setCookie\(|hono\/cookie|Set-Cookie/;
|
|
12275
12322
|
RE_LOGGER = /from ["'](pino|hono\/logger|winston|bunyan)["']|require\(["'](pino|winston|bunyan)["']\)|^[^/*]*\bconsole\.(log|info|warn|error)\(/;
|
|
12276
|
-
RE_EXPOSURE = /ZitadelAppIdentity|createOAuthMiddleware|createAuthorizationMiddleware|zitadelJwtScheme|createAuthzMiddleware|bearerAuth|type: *"oidc"|^\s+auth: *\{|_ROLES *= *\{|coarseRoles|ZITADEL_(AUDIENCE|CLIENT_ID)|OIDC_CLIENT_ID|zitadel\.(ApplicationOidc|ApplicationApi|Project|UserGrant)\(/;
|
|
12323
|
+
RE_EXPOSURE = /ZitadelAppIdentity|createOAuthMiddleware|createAuthorizationMiddleware|zitadelJwtScheme|createAuthzMiddleware|bearerAuth|type: *"oidc"|^\s+auth: *\{|_ROLES *= *\{|coarseRoles|ZITADEL_(AUDIENCE|CLIENT_ID)|OIDC_CLIENT_ID|zitadel\.(ApplicationOidc|ApplicationApi|Project|UserGrant)\(|mesh\.apps\.EventsService\(/;
|
|
12277
12324
|
RE_POINTER = /AppAuthzPointer/;
|
|
12278
12325
|
RE_METADATA = /compileOpsHubMetadata|ops-hub-metadata|SpiceDBSchema/;
|
|
12279
12326
|
RE_IAC_GRANT = /userId: *"[0-9]{6,}"/;
|
|
@@ -12284,6 +12331,8 @@ var init_app_check = __esm({
|
|
|
12284
12331
|
RE_SURFACE_NEW = /new\s+mesh\.apps\.(ApiSurface|AppApiSurface|VendorApiSurface|Integration)\s*\(/g;
|
|
12285
12332
|
RE_APIDOCS_NEW = /new\s+mesh\.apps\.ApiDocs\s*\(/g;
|
|
12286
12333
|
RE_IDENTITY_NEW = /new\s+mesh\.auth\.(ZitadelAppIdentity)\s*\(/g;
|
|
12334
|
+
RE_EVENTS_NEW = /new\s+mesh\.apps\.(EventsService)\s*\(/g;
|
|
12335
|
+
EVENTS_PROJECT_ROLES = ["events:subscriber", "events:admin"];
|
|
12287
12336
|
RE_IDENTITY_BINDING = /\b(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*(?::[^=]+)?=\s*new\s+mesh\.auth\.ZitadelAppIdentity\s*\(/g;
|
|
12288
12337
|
DEFAULT_PARTNER_DOCS_ROLES = ["docs"];
|
|
12289
12338
|
DOCS_SITE_FIELDS = ["pages", "playground", "audience", "basePath", "ingress", "auth"];
|
|
@@ -13507,7 +13556,13 @@ var init_scaffold_versions = __esm({
|
|
|
13507
13556
|
"use strict";
|
|
13508
13557
|
SCAFFOLD_PACKAGE_LINES = {
|
|
13509
13558
|
"@mesh-tech/app-kit": "^1.45.0",
|
|
13510
|
-
"@mesh-tech/esm-bundle": "^0.1.1"
|
|
13559
|
+
"@mesh-tech/esm-bundle": "^0.1.1",
|
|
13560
|
+
// The events service versions with the primitive that deploys it, not with
|
|
13561
|
+
// the lockstep group, so it carries its own floor.
|
|
13562
|
+
"@mesh-tech/events-service": "^0.1.0",
|
|
13563
|
+
// Likewise authz: it is not in the `fixed` group in .changeset/config.json,
|
|
13564
|
+
// so the lockstep range resolves to a major line it has never published.
|
|
13565
|
+
"@mesh-tech/authz": "^1.19.0"
|
|
13511
13566
|
};
|
|
13512
13567
|
SCAFFOLD_TOOLCHAIN = {
|
|
13513
13568
|
"@types/node": "^24.2.1",
|
|
@@ -13676,6 +13731,10 @@ async function promptForOptions(options) {
|
|
|
13676
13731
|
primitives.push("service");
|
|
13677
13732
|
logInfo("Added 'service' \u2014 bucket requires an API service for S3 helpers.");
|
|
13678
13733
|
}
|
|
13734
|
+
if (primitives.includes("events") && !primitives.includes("service")) {
|
|
13735
|
+
primitives.push("service");
|
|
13736
|
+
logInfo("Added 'service' \u2014 the events publisher lives in the api/ directory.");
|
|
13737
|
+
}
|
|
13679
13738
|
return { tenant, name, primitives };
|
|
13680
13739
|
}
|
|
13681
13740
|
function copyTemplate(srcDir, destDir, context) {
|
|
@@ -13787,6 +13846,13 @@ function generateComposableApp(appDir, context) {
|
|
|
13787
13846
|
}
|
|
13788
13847
|
copyTemplate(bucketDir, appDir, context);
|
|
13789
13848
|
}
|
|
13849
|
+
if (context.events) {
|
|
13850
|
+
const eventsDir = path27.join(fragmentsDir, "events");
|
|
13851
|
+
if (!fs25.existsSync(eventsDir)) {
|
|
13852
|
+
throw new Error(`Fragment directory not found: ${eventsDir}`);
|
|
13853
|
+
}
|
|
13854
|
+
copyTemplate(eventsDir, appDir, context);
|
|
13855
|
+
}
|
|
13790
13856
|
const agentsDir = path27.join(fragmentsDir, "agents");
|
|
13791
13857
|
if (fs25.existsSync(agentsDir)) {
|
|
13792
13858
|
copyTemplate(agentsDir, appDir, context);
|
|
@@ -13853,6 +13919,10 @@ function registerCreateAppCommand(program2) {
|
|
|
13853
13919
|
primitives.push("service");
|
|
13854
13920
|
logInfo("Added 'service' \u2014 bucket requires an API service for S3 helpers.");
|
|
13855
13921
|
}
|
|
13922
|
+
if (primitives.includes("events") && !primitives.includes("service")) {
|
|
13923
|
+
primitives.push("service");
|
|
13924
|
+
logInfo("Added 'service' \u2014 the events publisher lives in the api/ directory.");
|
|
13925
|
+
}
|
|
13856
13926
|
await runComposable(tenant, name, primitives, options.test);
|
|
13857
13927
|
});
|
|
13858
13928
|
}
|
|
@@ -13961,6 +14031,8 @@ async function runComposable(tenant, name, primitives, test) {
|
|
|
13961
14031
|
const repoRoot2 = resolveTargetRoot(process.cwd());
|
|
13962
14032
|
const mesh = workspaceDeps ? { range: "", resolved: true } : resolvePublishedRange("@mesh-tech/app-kit", repoRoot2);
|
|
13963
14033
|
const esmBundle = workspaceDeps ? { range: "", resolved: true } : resolvePublishedRange("@mesh-tech/esm-bundle", repoRoot2);
|
|
14034
|
+
const eventsService = workspaceDeps || !primitives.includes("events") ? { range: "", resolved: true } : resolvePublishedRange("@mesh-tech/events-service", repoRoot2);
|
|
14035
|
+
const authz = workspaceDeps || !primitives.includes("events") ? { range: "", resolved: true } : resolvePublishedRange("@mesh-tech/authz", repoRoot2);
|
|
13964
14036
|
if (!workspaceDeps) {
|
|
13965
14037
|
logInfo(
|
|
13966
14038
|
mesh.resolved ? `@mesh-tech/* pinned to ${mesh.range} (current published line)` : `Registry lookup unavailable \u2014 pinning @mesh-tech/* to ${mesh.range}; run \`pnpm up @mesh-tech/*\` once you have registry auth`
|
|
@@ -13973,10 +14045,13 @@ async function runComposable(tenant, name, primitives, test) {
|
|
|
13973
14045
|
database: primitives.includes("database"),
|
|
13974
14046
|
temporal: primitives.includes("temporal"),
|
|
13975
14047
|
bucket: primitives.includes("bucket"),
|
|
14048
|
+
events: primitives.includes("events"),
|
|
13976
14049
|
region,
|
|
13977
14050
|
workspaceDeps,
|
|
13978
14051
|
meshRange: mesh.range,
|
|
13979
14052
|
esmBundleRange: esmBundle.range,
|
|
14053
|
+
eventsServiceRange: eventsService.range,
|
|
14054
|
+
authzRange: authz.range,
|
|
13980
14055
|
typesNodeRange: SCAFFOLD_TOOLCHAIN["@types/node"],
|
|
13981
14056
|
tsxRange: SCAFFOLD_TOOLCHAIN.tsx,
|
|
13982
14057
|
typescriptRange: SCAFFOLD_TOOLCHAIN.typescript,
|
|
@@ -14091,7 +14166,8 @@ var init_create_app = __esm({
|
|
|
14091
14166
|
service: "HTTP Service (API with Hono)",
|
|
14092
14167
|
database: "Database (PostgreSQL via Prisma)",
|
|
14093
14168
|
temporal: "Temporal (workflow orchestration)",
|
|
14094
|
-
bucket: "S3 Bucket (file storage)"
|
|
14169
|
+
bucket: "S3 Bucket (file storage)",
|
|
14170
|
+
events: "Events and webhooks (publish, subscriptions, signed delivery)"
|
|
14095
14171
|
};
|
|
14096
14172
|
HUB_ACCOUNTS = {
|
|
14097
14173
|
mesh: "159923586610"
|