@mesh-tech/mesh-cli 0.19.0 → 0.20.1
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/README.md +7 -3
- package/dist/bin/mesh.js +289 -28
- package/dist/bin/mesh.js.map +4 -4
- package/dist/build-info.json +2 -2
- package/dist/src/commands/app-check.d.ts +1 -1
- package/dist/src/commands/app-check.d.ts.map +1 -1
- package/dist/src/commands/app-check.js +13 -0
- package/dist/src/commands/app-check.js.map +1 -1
- package/dist/src/commands/dev.d.ts +7 -0
- package/dist/src/commands/dev.d.ts.map +1 -1
- package/dist/src/commands/dev.js +1 -0
- package/dist/src/commands/dev.js.map +1 -1
- package/dist/src/commands/hub/index.d.ts.map +1 -1
- package/dist/src/commands/hub/index.js +5 -0
- package/dist/src/commands/hub/index.js.map +1 -1
- package/dist/src/commands/local/auth-provision.d.ts +4 -0
- package/dist/src/commands/local/auth-provision.d.ts.map +1 -1
- package/dist/src/commands/local/auth-provision.js +20 -0
- package/dist/src/commands/local/auth-provision.js.map +1 -1
- package/dist/src/commands/local/dev-local.d.ts +32 -0
- package/dist/src/commands/local/dev-local.d.ts.map +1 -1
- package/dist/src/commands/local/dev-local.js +82 -2
- package/dist/src/commands/local/dev-local.js.map +1 -1
- package/dist/src/commands/local/hub-local.d.ts +35 -0
- package/dist/src/commands/local/hub-local.d.ts.map +1 -1
- package/dist/src/commands/local/hub-local.js +61 -1
- package/dist/src/commands/local/hub-local.js.map +1 -1
- package/dist/src/commands/local/index.d.ts.map +1 -1
- package/dist/src/commands/local/index.js +15 -1
- package/dist/src/commands/local/index.js.map +1 -1
- package/dist/src/commands/local/seed-hub-catalog.d.ts +33 -0
- package/dist/src/commands/local/seed-hub-catalog.d.ts.map +1 -0
- package/dist/src/commands/local/seed-hub-catalog.js +90 -0
- package/dist/src/commands/local/seed-hub-catalog.js.map +1 -0
- package/dist/src/commands/local/seed.d.ts +9 -0
- package/dist/src/commands/local/seed.d.ts.map +1 -1
- package/dist/src/commands/local/seed.js +49 -10
- package/dist/src/commands/local/seed.js.map +1 -1
- package/dist/src/commands/login.d.ts +12 -0
- package/dist/src/commands/login.d.ts.map +1 -1
- package/dist/src/commands/login.js +52 -7
- package/dist/src/commands/login.js.map +1 -1
- package/dist/src/commands/registry.d.ts +22 -3
- package/dist/src/commands/registry.d.ts.map +1 -1
- package/dist/src/commands/registry.js +36 -4
- package/dist/src/commands/registry.js.map +1 -1
- package/dist/src/commands/temporal.js +37 -7
- package/dist/src/commands/temporal.js.map +2 -2
- package/dist/src/utils/aws-auth.d.ts +19 -0
- package/dist/src/utils/aws-auth.d.ts.map +1 -1
- package/dist/src/utils/aws-auth.js +30 -9
- package/dist/src/utils/aws-auth.js.map +1 -1
- package/dist/src/utils/registry-identity.d.ts +35 -0
- package/dist/src/utils/registry-identity.d.ts.map +1 -1
- package/dist/src/utils/registry-identity.js +47 -1
- package/dist/src/utils/registry-identity.js.map +1 -1
- package/package.json +2 -2
- package/skills/core/SKILL.md +3 -3
- package/templates/api-auth/index.ts.hbs +3 -1
- package/templates/api-role-gating/index.ts.hbs +3 -1
- package/templates/apps-repo/gitignore +4 -0
package/README.md
CHANGED
|
@@ -64,7 +64,9 @@ trip).
|
|
|
64
64
|
**If your account does not hold the grant yet**, the command tells you so and
|
|
65
65
|
prints the access-request URL the platform returned (or names the admin to
|
|
66
66
|
ask). Contact your Mesh contact to have **registry access granted to your
|
|
67
|
-
user**, then run `mesh registry login` again
|
|
67
|
+
user**, then run `mesh registry login` again — it re-mints your sign-in so the
|
|
68
|
+
new role is in the token the broker sees. If it still says not authorized,
|
|
69
|
+
start a fresh sign-in: `mesh registry logout && mesh registry login`.
|
|
68
70
|
|
|
69
71
|
Everything after this point — installing `@mesh-tech/*` packages, scaffolding
|
|
70
72
|
an app, and the local docs server — depends on this step. `mesh init` (next)
|
|
@@ -241,8 +243,10 @@ everywhere else.
|
|
|
241
243
|
|
|
242
244
|
Your account and session are fine; you don't hold the registry grant yet. The
|
|
243
245
|
message names the request URL (or the admin to ask) — see
|
|
244
|
-
[step 2](#2-get-registry-access). Once granted, `mesh registry login` again
|
|
245
|
-
your sign-in
|
|
246
|
+
[step 2](#2-get-registry-access). Once granted, `mesh registry login` again —
|
|
247
|
+
it re-mints your sign-in so the new role reaches the broker (a role only rides
|
|
248
|
+
in tokens issued after it was granted). Still refused?
|
|
249
|
+
`mesh registry logout && mesh registry login` starts a fresh sign-in.
|
|
246
250
|
|
|
247
251
|
### `No registry access on this machine — @mesh-tech packages cannot be installed.`
|
|
248
252
|
|
package/dist/bin/mesh.js
CHANGED
|
@@ -767,22 +767,35 @@ async function seedLocalPlatform() {
|
|
|
767
767
|
);
|
|
768
768
|
await registerTenantEnv(LOCAL_TENANT, { tier: "hub" });
|
|
769
769
|
logSuccess(`Registered tenant '${LOCAL_TENANT}' env '${LOCAL_ENV}' \u2192 ${APP_TENANTS_PARAM}`);
|
|
770
|
-
const { S3Client, CreateBucketCommand } = await import("@aws-sdk/client-s3");
|
|
770
|
+
const { S3Client, CreateBucketCommand, PutObjectCommand } = await import("@aws-sdk/client-s3");
|
|
771
771
|
const s3 = new S3Client({ ...AWS_CONFIG, forcePathStyle: true });
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
772
|
+
for (const bucket of [ARTIFACTS_BUCKET, DATA_BUCKET]) {
|
|
773
|
+
try {
|
|
774
|
+
await s3.send(new CreateBucketCommand({ Bucket: bucket }));
|
|
775
|
+
logSuccess(`Created bucket s3://${bucket}`);
|
|
776
|
+
} catch (err) {
|
|
777
|
+
if (err?.name === "BucketAlreadyOwnedByYou" || err?.name === "BucketAlreadyExists") {
|
|
778
|
+
logInfo(`Bucket s3://${bucket} already exists`);
|
|
779
|
+
} else {
|
|
780
|
+
throw err;
|
|
781
|
+
}
|
|
780
782
|
}
|
|
781
783
|
}
|
|
784
|
+
for (const seed of DATA_BUCKET_SEEDS) {
|
|
785
|
+
await s3.send(
|
|
786
|
+
new PutObjectCommand({
|
|
787
|
+
Bucket: DATA_BUCKET,
|
|
788
|
+
Key: seed.key,
|
|
789
|
+
Body: seed.body,
|
|
790
|
+
ContentType: "application/json"
|
|
791
|
+
})
|
|
792
|
+
);
|
|
793
|
+
}
|
|
794
|
+
logSuccess(`Seeded s3://${DATA_BUCKET} with ${DATA_BUCKET_SEEDS.length} sample object(s)`);
|
|
782
795
|
await seedTemporalNamespace();
|
|
783
796
|
return { parameter: APP_TENANTS_PARAM, bucket: ARTIFACTS_BUCKET };
|
|
784
797
|
}
|
|
785
|
-
var LOCAL_TENANT, LOCAL_ENV, LOCAL_AWS_ENDPOINT, LOCAL_AWS_REGION, ARTIFACTS_BUCKET, APP_TENANTS_PARAM, LOCAL_AWS_CONFIG, AWS_CONFIG, FABRIC_CHECK_PATH, TEMPORAL_NAMESPACE, TEMPORAL_ADDRESS;
|
|
798
|
+
var LOCAL_TENANT, LOCAL_ENV, LOCAL_AWS_ENDPOINT, LOCAL_AWS_REGION, ARTIFACTS_BUCKET, DATA_BUCKET, APP_TENANTS_PARAM, DATA_BUCKET_SEEDS, LOCAL_AWS_CONFIG, AWS_CONFIG, FABRIC_CHECK_PATH, TEMPORAL_NAMESPACE, TEMPORAL_ADDRESS;
|
|
786
799
|
var init_seed = __esm({
|
|
787
800
|
"libs/mesh-cli/src/commands/local/seed.ts"() {
|
|
788
801
|
"use strict";
|
|
@@ -793,7 +806,24 @@ var init_seed = __esm({
|
|
|
793
806
|
LOCAL_AWS_ENDPOINT = "http://localhost:4566";
|
|
794
807
|
LOCAL_AWS_REGION = "us-east-2";
|
|
795
808
|
ARTIFACTS_BUCKET = "mesh-local-artifacts";
|
|
809
|
+
DATA_BUCKET = "mesh-local-data";
|
|
796
810
|
APP_TENANTS_PARAM = `/mesh-platform/${LOCAL_TENANT}/${LOCAL_ENV}/app-tenants`;
|
|
811
|
+
DATA_BUCKET_SEEDS = [
|
|
812
|
+
{
|
|
813
|
+
key: "samples/pipes/fragment-0001.json",
|
|
814
|
+
body: JSON.stringify(
|
|
815
|
+
{
|
|
816
|
+
id: "sample-0001",
|
|
817
|
+
kind: "pipes-fragment",
|
|
818
|
+
account: "0001",
|
|
819
|
+
asOf: "2026-01-01",
|
|
820
|
+
lines: [{ amount: "125.00", currency: "USD", description: "Sample credit" }]
|
|
821
|
+
},
|
|
822
|
+
null,
|
|
823
|
+
2
|
|
824
|
+
)
|
|
825
|
+
}
|
|
826
|
+
];
|
|
797
827
|
LOCAL_AWS_CONFIG = {
|
|
798
828
|
endpoint: LOCAL_AWS_ENDPOINT,
|
|
799
829
|
region: LOCAL_AWS_REGION,
|
|
@@ -3244,6 +3274,7 @@ __export(login_exports, {
|
|
|
3244
3274
|
discoverConfigForContext: () => discoverConfigForContext,
|
|
3245
3275
|
discoverConfigFromWellKnown: () => discoverConfigFromWellKnown,
|
|
3246
3276
|
ensureLogin: () => ensureLogin,
|
|
3277
|
+
forceRefreshToken: () => forceRefreshToken,
|
|
3247
3278
|
getContextConfig: () => getContextConfig,
|
|
3248
3279
|
getValidToken: () => getValidToken,
|
|
3249
3280
|
isRemoteEnvironment: () => isRemoteEnvironment,
|
|
@@ -3963,22 +3994,43 @@ async function getValidToken(context, opts = {}) {
|
|
|
3963
3994
|
if (tokenStillValid(creds.expiresAt, opts.marginMs ?? 0)) {
|
|
3964
3995
|
return creds.idToken;
|
|
3965
3996
|
}
|
|
3997
|
+
return remintToken(context, config, creds);
|
|
3998
|
+
}
|
|
3999
|
+
async function remintToken(context, config, creds) {
|
|
3966
4000
|
if (!creds.refreshToken) return null;
|
|
4001
|
+
let tokens;
|
|
4002
|
+
try {
|
|
4003
|
+
tokens = await refreshTokens(config.issuer, config.clientId, creds.refreshToken);
|
|
4004
|
+
} catch {
|
|
4005
|
+
return null;
|
|
4006
|
+
}
|
|
4007
|
+
let email = creds.email;
|
|
4008
|
+
try {
|
|
4009
|
+
email = decodeJwtPayload(tokens.id_token).email ?? creds.email;
|
|
4010
|
+
} catch {
|
|
4011
|
+
}
|
|
3967
4012
|
try {
|
|
3968
|
-
const tokens = await refreshTokens(config.issuer, config.clientId, creds.refreshToken);
|
|
3969
|
-
const idPayload = decodeJwtPayload(tokens.id_token);
|
|
3970
4013
|
writeCredentials(context, {
|
|
3971
4014
|
...creds,
|
|
3972
4015
|
idToken: tokens.id_token,
|
|
3973
4016
|
accessToken: tokens.access_token,
|
|
3974
4017
|
refreshToken: tokens.refresh_token ?? creds.refreshToken,
|
|
3975
4018
|
expiresAt: new Date(Date.now() + tokens.expires_in * 1e3).toISOString(),
|
|
3976
|
-
email
|
|
4019
|
+
email
|
|
3977
4020
|
});
|
|
3978
|
-
|
|
3979
|
-
|
|
3980
|
-
|
|
4021
|
+
} catch (err) {
|
|
4022
|
+
logWarn(
|
|
4023
|
+
`The refreshed ${context} session could not be cached (${err instanceof Error ? err.message : String(err)}) \u2014 this run continues on the new token, but the next one may need a fresh sign-in.`
|
|
4024
|
+
);
|
|
3981
4025
|
}
|
|
4026
|
+
return tokens.id_token;
|
|
4027
|
+
}
|
|
4028
|
+
async function forceRefreshToken(context) {
|
|
4029
|
+
const config = getContextConfig(context);
|
|
4030
|
+
if (!config) return null;
|
|
4031
|
+
const creds = readCredentials(context);
|
|
4032
|
+
if (!creds) return null;
|
|
4033
|
+
return remintToken(context, config, creds);
|
|
3982
4034
|
}
|
|
3983
4035
|
async function probeCredentials(context, roleArn) {
|
|
3984
4036
|
const cached = readCredentials(context);
|
|
@@ -4346,7 +4398,9 @@ __export(registry_identity_exports, {
|
|
|
4346
4398
|
isReservedRegistryContext: () => isReservedRegistryContext,
|
|
4347
4399
|
logRegistryIdentity: () => logRegistryIdentity,
|
|
4348
4400
|
readRegistrySession: () => readRegistrySession,
|
|
4401
|
+
refreshRegistrySession: () => refreshRegistrySession,
|
|
4349
4402
|
registrySessionCandidates: () => registrySessionCandidates,
|
|
4403
|
+
registryTokenRoles: () => registryTokenRoles,
|
|
4350
4404
|
resolveRegistryIdentity: () => resolveRegistryIdentity
|
|
4351
4405
|
});
|
|
4352
4406
|
function isReservedRegistryContext(context) {
|
|
@@ -4450,6 +4504,22 @@ async function ensureRegistrySession(opts) {
|
|
|
4450
4504
|
expiresAt: creds.expiresAt
|
|
4451
4505
|
};
|
|
4452
4506
|
}
|
|
4507
|
+
function registryTokenRoles(idToken) {
|
|
4508
|
+
if (!idToken) return [];
|
|
4509
|
+
try {
|
|
4510
|
+
return projectRoleKeys(decodeJwtPayload(idToken)).sort();
|
|
4511
|
+
} catch {
|
|
4512
|
+
return [];
|
|
4513
|
+
}
|
|
4514
|
+
}
|
|
4515
|
+
async function refreshRegistrySession(context) {
|
|
4516
|
+
const before = registryTokenRoles(readCredentials(context)?.idToken);
|
|
4517
|
+
const idToken = await forceRefreshToken(context);
|
|
4518
|
+
if (!idToken) return { reminted: false, rolesChanged: false };
|
|
4519
|
+
const after = registryTokenRoles(idToken);
|
|
4520
|
+
const same = after.length === before.length && after.every((role, i) => role === before[i]);
|
|
4521
|
+
return { reminted: true, rolesChanged: !same };
|
|
4522
|
+
}
|
|
4453
4523
|
function clearRegistrySession() {
|
|
4454
4524
|
clearCredentials(REGISTRY_CREDENTIAL_KEY);
|
|
4455
4525
|
clearContextConfig(REGISTRY_CREDENTIAL_KEY);
|
|
@@ -4478,6 +4548,7 @@ var init_registry_identity = __esm({
|
|
|
4478
4548
|
"libs/mesh-cli/src/utils/registry-identity.ts"() {
|
|
4479
4549
|
"use strict";
|
|
4480
4550
|
init_login();
|
|
4551
|
+
init_aws_auth();
|
|
4481
4552
|
init_first_party_contexts();
|
|
4482
4553
|
init_registry_broker();
|
|
4483
4554
|
init_errors();
|
|
@@ -4955,16 +5026,20 @@ function selectRoleForCaller(idToken, options) {
|
|
|
4955
5026
|
const adminRoles = options.adminClaimRoles ?? ["mesh.platform:admin"];
|
|
4956
5027
|
const claims = decodeJwtPayload2(idToken);
|
|
4957
5028
|
if (!claims) return options.defaultRole;
|
|
5029
|
+
const claimedRoles = projectRoleKeys(claims);
|
|
5030
|
+
return adminRoles.some((target) => claimedRoles.includes(target)) ? options.adminRole : options.defaultRole;
|
|
5031
|
+
}
|
|
5032
|
+
function projectRoleKeys(claims) {
|
|
5033
|
+
const names = /* @__PURE__ */ new Set();
|
|
4958
5034
|
for (const [key, value] of Object.entries(claims)) {
|
|
4959
5035
|
if (!key.startsWith("urn:zitadel:iam:org:project:")) continue;
|
|
4960
5036
|
if (!key.endsWith(":roles")) continue;
|
|
4961
|
-
if (!value || typeof value !== "object") continue;
|
|
4962
|
-
const
|
|
4963
|
-
|
|
4964
|
-
if (claimedRoles.includes(target)) return options.adminRole;
|
|
5037
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) continue;
|
|
5038
|
+
for (const name of Object.keys(value)) {
|
|
5039
|
+
if (name) names.add(name);
|
|
4965
5040
|
}
|
|
4966
5041
|
}
|
|
4967
|
-
return
|
|
5042
|
+
return [...names];
|
|
4968
5043
|
}
|
|
4969
5044
|
async function resolveAwsCredentials(roleArn, appRoot, stack) {
|
|
4970
5045
|
const roleName = roleArn.split("/").pop() ?? roleArn;
|
|
@@ -6808,6 +6883,20 @@ async function registerLocalApp(args) {
|
|
|
6808
6883
|
`Local service registration (${service})`
|
|
6809
6884
|
);
|
|
6810
6885
|
}
|
|
6886
|
+
for (const service of args.services) {
|
|
6887
|
+
if (args.kinds?.[service] !== "dagster") continue;
|
|
6888
|
+
const port = args.ports?.[service];
|
|
6889
|
+
if (!port) continue;
|
|
6890
|
+
await put2(
|
|
6891
|
+
`${base}/dagster`,
|
|
6892
|
+
{
|
|
6893
|
+
kind: "dagster",
|
|
6894
|
+
graphqlUrl: `http://host.docker.internal:${port}/graphql`,
|
|
6895
|
+
uiUrl: `http://localhost:${port}`
|
|
6896
|
+
},
|
|
6897
|
+
`Local Dagster discovery record (${service})`
|
|
6898
|
+
);
|
|
6899
|
+
}
|
|
6811
6900
|
for (const service of args.services) {
|
|
6812
6901
|
if (!/worker/i.test(service)) continue;
|
|
6813
6902
|
await put2(
|
|
@@ -7127,6 +7216,22 @@ function localPlatformEnv(tenant, appName, serviceName) {
|
|
|
7127
7216
|
}
|
|
7128
7217
|
return env;
|
|
7129
7218
|
}
|
|
7219
|
+
function isDagsterWorkspaceRoot(appRoot) {
|
|
7220
|
+
if (fs17.existsSync(path17.join(appRoot, "dg.toml"))) return true;
|
|
7221
|
+
const pyproject = path17.join(appRoot, "pyproject.toml");
|
|
7222
|
+
if (!fs17.existsSync(pyproject)) return false;
|
|
7223
|
+
try {
|
|
7224
|
+
return /^\s*\[tool\.dg[\].]/m.test(fs17.readFileSync(pyproject, "utf-8"));
|
|
7225
|
+
} catch {
|
|
7226
|
+
return false;
|
|
7227
|
+
}
|
|
7228
|
+
}
|
|
7229
|
+
function resolveDgBinary(appRoot) {
|
|
7230
|
+
for (const candidate of ["deployments/local/.venv/bin/dg", ".venv/bin/dg"]) {
|
|
7231
|
+
if (fs17.existsSync(path17.join(appRoot, candidate))) return candidate;
|
|
7232
|
+
}
|
|
7233
|
+
return "dg";
|
|
7234
|
+
}
|
|
7130
7235
|
function hasDevScript(dir) {
|
|
7131
7236
|
const pkgPath = path17.join(dir, "package.json");
|
|
7132
7237
|
if (!fs17.existsSync(pkgPath)) return false;
|
|
@@ -7153,9 +7258,10 @@ function detectLocalServices(appRoot) {
|
|
|
7153
7258
|
function buildLocalDevOutput(appRoot, tenant, opts = {}) {
|
|
7154
7259
|
const detected = detectLocalServices(appRoot);
|
|
7155
7260
|
const names = Object.keys(detected);
|
|
7156
|
-
|
|
7261
|
+
const isDagsterWorkspace = isDagsterWorkspaceRoot(appRoot);
|
|
7262
|
+
if (names.length === 0 && !isDagsterWorkspace) {
|
|
7157
7263
|
throw new MeshCliError(
|
|
7158
|
-
`No runnable services found under ${appRoot} \u2014 expected subdirectories (api/, worker/, \u2026) with a package.json 'dev' script.`,
|
|
7264
|
+
`No runnable services found under ${appRoot} \u2014 expected subdirectories (api/, worker/, \u2026) with a package.json 'dev' script, or a Dagster workspace (dg.toml, or [tool.dg] in pyproject.toml).`,
|
|
7159
7265
|
{ remediation: { command: "mesh create-app" } }
|
|
7160
7266
|
);
|
|
7161
7267
|
}
|
|
@@ -7203,6 +7309,25 @@ function buildLocalDevOutput(appRoot, tenant, opts = {}) {
|
|
|
7203
7309
|
services[other].env[key] = { value: url };
|
|
7204
7310
|
}
|
|
7205
7311
|
}
|
|
7312
|
+
if (isDagsterWorkspace) {
|
|
7313
|
+
const port = BASE_PORT + names.length + mockNames.length;
|
|
7314
|
+
services["dagster"] = {
|
|
7315
|
+
// Absolute on purpose: a relative src is MONOREPO-ROOT-relative by the
|
|
7316
|
+
// ServiceDevOutput contract (rebaseServiceSrc), which for an app nested
|
|
7317
|
+
// in the monorepo would cd the launch shell to the repo root — where
|
|
7318
|
+
// neither dg.toml nor the venv path resolves. The absolute app root
|
|
7319
|
+
// survives rebasing in both the monorepo and standalone-repo shapes.
|
|
7320
|
+
src: appRoot,
|
|
7321
|
+
kind: "dagster",
|
|
7322
|
+
port,
|
|
7323
|
+
command: [resolveDgBinary(appRoot), "dev", "-h", "127.0.0.1", "-p", '"$PORT"'],
|
|
7324
|
+
env: {
|
|
7325
|
+
PORT: { value: String(port) },
|
|
7326
|
+
DAGSTER_HOME: { value: path17.join(appRoot, ".dagster") },
|
|
7327
|
+
...localPlatformEnv(tenant, appName, "dagster")
|
|
7328
|
+
}
|
|
7329
|
+
};
|
|
7330
|
+
}
|
|
7206
7331
|
mockNames.forEach((name, i) => {
|
|
7207
7332
|
const decl = mocks[name];
|
|
7208
7333
|
const serviceName = `mock-${name}`;
|
|
@@ -9349,6 +9474,11 @@ function registerDevCommand(program2) {
|
|
|
9349
9474
|
ports: Object.fromEntries(
|
|
9350
9475
|
Object.entries(devOutput2.services).map(([name, svc]) => [name, svc.port])
|
|
9351
9476
|
),
|
|
9477
|
+
kinds: Object.fromEntries(
|
|
9478
|
+
Object.entries(devOutput2.services).flatMap(
|
|
9479
|
+
([name, svc]) => svc.kind ? [[name, svc.kind]] : []
|
|
9480
|
+
)
|
|
9481
|
+
),
|
|
9352
9482
|
// ExternalService.link() parity: consuming services carry the
|
|
9353
9483
|
// external names, so the Hub shows consumers + per-app uptime.
|
|
9354
9484
|
links: Object.values(selectedMocks).map((decl) => decl.external).filter((n) => !!n)
|
|
@@ -10663,6 +10793,11 @@ function scanApp(root, appRel) {
|
|
|
10663
10793
|
for (const f of files) hit(code, rel(root, f), why);
|
|
10664
10794
|
};
|
|
10665
10795
|
anti("IAC_GRANTS", RE_IAC_GRANT, "per-user role grant baked into IaC \u2014 grants belong in the Hub (Access \u2192 People), not in a config redeploy");
|
|
10796
|
+
anti(
|
|
10797
|
+
"POINTER_PROJECT_OVERRIDE",
|
|
10798
|
+
RE_POINTER_OVERRIDE,
|
|
10799
|
+
"AppAuthzPointer is given a `zitadel:` override \u2014 an app binds its project with AppEnvironment's zitadelAppProjectId so every consumer of env.zitadel agrees; the override is reserved for the Hub"
|
|
10800
|
+
);
|
|
10666
10801
|
anti(
|
|
10667
10802
|
"PASSWORD_STORE",
|
|
10668
10803
|
RE_PASSWORD,
|
|
@@ -10772,7 +10907,7 @@ function registerAppCommands(program2) {
|
|
|
10772
10907
|
}
|
|
10773
10908
|
});
|
|
10774
10909
|
}
|
|
10775
|
-
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_PASSWORD, RE_ALLOWLIST, RE_USERS_TABLE;
|
|
10910
|
+
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;
|
|
10776
10911
|
var init_app_check = __esm({
|
|
10777
10912
|
"libs/mesh-cli/src/commands/app-check.ts"() {
|
|
10778
10913
|
"use strict";
|
|
@@ -10844,6 +10979,13 @@ var init_app_check = __esm({
|
|
|
10844
10979
|
remediation: "remove the per-user grant map; grant roles from the Hub (App \u2192 Access \u2192 People)",
|
|
10845
10980
|
scope: "app"
|
|
10846
10981
|
},
|
|
10982
|
+
POINTER_PROJECT_OVERRIDE: {
|
|
10983
|
+
gate: "H",
|
|
10984
|
+
title: "Pointer publishes the env's project",
|
|
10985
|
+
severity: "advisory",
|
|
10986
|
+
remediation: "drop `zitadel:` from AppAuthzPointer and pass `zitadelAppProjectId: identity.projectId` to AppEnvironment \u2014 the override is the Hub's",
|
|
10987
|
+
scope: "app"
|
|
10988
|
+
},
|
|
10847
10989
|
PASSWORD_STORE: {
|
|
10848
10990
|
gate: "H",
|
|
10849
10991
|
title: "No app-side credentials",
|
|
@@ -10891,6 +11033,7 @@ var init_app_check = __esm({
|
|
|
10891
11033
|
RE_POINTER = /AppAuthzPointer/;
|
|
10892
11034
|
RE_METADATA = /compileOpsHubMetadata|ops-hub-metadata|SpiceDBSchema/;
|
|
10893
11035
|
RE_IAC_GRANT = /userId: *"[0-9]{6,}"/;
|
|
11036
|
+
RE_POINTER_OVERRIDE = /\bzitadel: *\{\s*projectId\b/;
|
|
10894
11037
|
RE_PASSWORD = /(^|[^a-z])(bcrypt|argon2)|password_hash|passwordHash|hashPassword|temporaryPassword.*(send|mail)|(send|mail).*temporaryPassword/;
|
|
10895
11038
|
RE_ALLOWLIST = /ADMIN_EMAILS|ALLOWED_EMAILS|allowedEmails|adminEmails/;
|
|
10896
11039
|
RE_USERS_TABLE = /create table (if not exists )?"?(users|roles|user_roles|permissions)"?/i;
|
|
@@ -15922,7 +16065,12 @@ var init_hub = __esm({
|
|
|
15922
16065
|
TEMPORAL_UI_URL: "http://localhost:8233",
|
|
15923
16066
|
// The stack's namespace. Absent, the Hub's compliance schedules fail to
|
|
15924
16067
|
// register with "Temporal namespace is required".
|
|
15925
|
-
TEMPORAL_NAMESPACE: "local-dev"
|
|
16068
|
+
TEMPORAL_NAMESPACE: "local-dev",
|
|
16069
|
+
// Dagster discovery records written by `mesh dev --local` carry
|
|
16070
|
+
// `host.docker.internal` (for the containerized `mesh start` hub). This hub
|
|
16071
|
+
// runs on the host, where that name does not resolve — tell the Data
|
|
16072
|
+
// section's resolver to swap it for localhost.
|
|
16073
|
+
MESH_DAGSTER_LOCAL_HOST: "localhost"
|
|
15926
16074
|
});
|
|
15927
16075
|
SENSITIVE_RE = /TOKEN|SECRET|KEY|PASSWORD/i;
|
|
15928
16076
|
}
|
|
@@ -16069,6 +16217,7 @@ __export(registry_exports, {
|
|
|
16069
16217
|
resolvePublisherRoleArn: () => resolvePublisherRoleArn,
|
|
16070
16218
|
runRegistryLogin: () => runRegistryLogin,
|
|
16071
16219
|
runRegistryStatus: () => runRegistryStatus,
|
|
16220
|
+
shouldRemintAfter: () => shouldRemintAfter,
|
|
16072
16221
|
shouldRepairUserNpmrc: () => shouldRepairUserNpmrc
|
|
16073
16222
|
});
|
|
16074
16223
|
import { execFileSync as execFileSync24 } from "child_process";
|
|
@@ -16332,6 +16481,9 @@ function describeBrokerFailure(failure) {
|
|
|
16332
16481
|
function registrySignInAllowed(opts, hasTty = Boolean(process.stdin.isTTY && process.stdout.isTTY)) {
|
|
16333
16482
|
return Boolean(opts.device) || hasTty;
|
|
16334
16483
|
}
|
|
16484
|
+
function shouldRemintAfter(failure) {
|
|
16485
|
+
return failure.kind === "not-authorized";
|
|
16486
|
+
}
|
|
16335
16487
|
async function tryBrokerLogin(opts) {
|
|
16336
16488
|
if (opts.broker === false) return { outcome: { kind: "skipped-flag", flag: "--no-broker" } };
|
|
16337
16489
|
if (opts.ci) return { outcome: { kind: "skipped-flag", flag: "--ci" } };
|
|
@@ -16371,6 +16523,13 @@ async function tryBrokerLogin(opts) {
|
|
|
16371
16523
|
result = await requestGrant(session.context, identity);
|
|
16372
16524
|
}
|
|
16373
16525
|
}
|
|
16526
|
+
if (!result.ok && shouldRemintAfter(result.failure)) {
|
|
16527
|
+
const { rolesChanged } = await refreshRegistrySession(session.context);
|
|
16528
|
+
if (rolesChanged) {
|
|
16529
|
+
logInfo("Your sign-in now carries a role it did not have \u2014 retrying\u2026");
|
|
16530
|
+
result = await requestGrant(session.context, identity);
|
|
16531
|
+
}
|
|
16532
|
+
}
|
|
16374
16533
|
if (result.ok) return { grant: result.grant };
|
|
16375
16534
|
const { message, hint, fatal } = describeBrokerFailure(result.failure);
|
|
16376
16535
|
if (fatal) {
|
|
@@ -16720,7 +16879,7 @@ var init_registry = __esm({
|
|
|
16720
16879
|
this.name = "RegistryNotAuthorizedError";
|
|
16721
16880
|
}
|
|
16722
16881
|
};
|
|
16723
|
-
NOT_AUTHORIZED_RERUN = "Once granted, run: mesh registry login (your sign-in
|
|
16882
|
+
NOT_AUTHORIZED_RERUN = "Once granted, run: mesh registry login (it re-mints your sign-in; still refused? mesh registry logout && mesh registry login)";
|
|
16724
16883
|
CONTEXT_IGNORED_NOTICE = "The registry is global \u2014 the context argument is no longer needed (ignored).";
|
|
16725
16884
|
}
|
|
16726
16885
|
});
|
|
@@ -17686,6 +17845,32 @@ async function ensureHubImages() {
|
|
|
17686
17845
|
}
|
|
17687
17846
|
return version;
|
|
17688
17847
|
}
|
|
17848
|
+
function tarballNameForImageVersion(version) {
|
|
17849
|
+
if (version.endsWith("-src")) return null;
|
|
17850
|
+
return `mesh-tech-hub-${version.replace(/-r\d+$/, "")}.tgz`;
|
|
17851
|
+
}
|
|
17852
|
+
function readHubCompiledAuthz(version) {
|
|
17853
|
+
const context = path40.join(cacheDir(), `context-${version}`);
|
|
17854
|
+
const file = path40.join(context, HUB_COMPILED_AUTHZ_REL);
|
|
17855
|
+
if (!fs33.existsSync(file)) {
|
|
17856
|
+
const tarball = tarballNameForImageVersion(version);
|
|
17857
|
+
if (!tarball || !fs33.existsSync(path40.join(cacheDir(), tarball))) return null;
|
|
17858
|
+
fs33.mkdirSync(context, { recursive: true });
|
|
17859
|
+
try {
|
|
17860
|
+
execFileSync26(
|
|
17861
|
+
"tar",
|
|
17862
|
+
["-xzf", path40.join(cacheDir(), tarball), "-C", context, "--strip-components", "1", `package/${HUB_COMPILED_AUTHZ_REL}`],
|
|
17863
|
+
{ stdio: ["ignore", "pipe", "pipe"] }
|
|
17864
|
+
);
|
|
17865
|
+
} catch {
|
|
17866
|
+
return null;
|
|
17867
|
+
}
|
|
17868
|
+
if (!fs33.existsSync(file)) return null;
|
|
17869
|
+
}
|
|
17870
|
+
const parsed = JSON.parse(fs33.readFileSync(file, "utf-8"));
|
|
17871
|
+
if (typeof parsed.zed !== "string" || !parsed.metadata || typeof parsed.metadata !== "object") return null;
|
|
17872
|
+
return { zed: parsed.zed, metadata: parsed.metadata };
|
|
17873
|
+
}
|
|
17689
17874
|
function readWorkspaceCatalog(repoRoot2) {
|
|
17690
17875
|
const text = fs33.readFileSync(path40.join(repoRoot2, "pnpm-workspace.yaml"), "utf-8");
|
|
17691
17876
|
const marker = "\ncatalog:\n";
|
|
@@ -17700,7 +17885,7 @@ function readWorkspaceCatalog(repoRoot2) {
|
|
|
17700
17885
|
return catalog;
|
|
17701
17886
|
}
|
|
17702
17887
|
function normalizeHubManifests(contextDir, catalog) {
|
|
17703
|
-
const DEP_FIELDS = ["dependencies", "
|
|
17888
|
+
const DEP_FIELDS = ["dependencies", "optionalDependencies", "peerDependencies"];
|
|
17704
17889
|
const touched = [];
|
|
17705
17890
|
for (const entry of fs33.readdirSync(contextDir, { withFileTypes: true })) {
|
|
17706
17891
|
if (!entry.isDirectory()) continue;
|
|
@@ -17708,6 +17893,10 @@ function normalizeHubManifests(contextDir, catalog) {
|
|
|
17708
17893
|
if (!fs33.existsSync(manifest)) continue;
|
|
17709
17894
|
const pkg = JSON.parse(fs33.readFileSync(manifest, "utf-8"));
|
|
17710
17895
|
let changed = false;
|
|
17896
|
+
if (pkg.devDependencies) {
|
|
17897
|
+
delete pkg.devDependencies;
|
|
17898
|
+
changed = true;
|
|
17899
|
+
}
|
|
17711
17900
|
for (const field of DEP_FIELDS) {
|
|
17712
17901
|
const deps = pkg[field];
|
|
17713
17902
|
if (!deps) continue;
|
|
@@ -17783,7 +17972,7 @@ async function buildHubImagesFromSource(repoRoot2) {
|
|
|
17783
17972
|
}
|
|
17784
17973
|
return version;
|
|
17785
17974
|
}
|
|
17786
|
-
var execFileAsync2, HUB_PACKAGE, HUB_IMAGES, LOCAL_IMAGE_REV, HUB_AUTH_IMAGE;
|
|
17975
|
+
var execFileAsync2, HUB_PACKAGE, HUB_IMAGES, LOCAL_IMAGE_REV, HUB_AUTH_IMAGE, HUB_COMPILED_AUTHZ_REL;
|
|
17787
17976
|
var init_hub_local = __esm({
|
|
17788
17977
|
"libs/mesh-cli/src/commands/local/hub-local.ts"() {
|
|
17789
17978
|
"use strict";
|
|
@@ -17797,6 +17986,66 @@ var init_hub_local = __esm({
|
|
|
17797
17986
|
HUB_IMAGES = ["mesh-local-hub-api", "mesh-local-hub-ui"];
|
|
17798
17987
|
LOCAL_IMAGE_REV = "r2";
|
|
17799
17988
|
HUB_AUTH_IMAGE = "mesh-local-hub-auth:v7.7.1-r1";
|
|
17989
|
+
HUB_COMPILED_AUTHZ_REL = "api/dist/authz/compiled.json";
|
|
17990
|
+
}
|
|
17991
|
+
});
|
|
17992
|
+
|
|
17993
|
+
// libs/mesh-cli/src/commands/local/seed-hub-catalog.ts
|
|
17994
|
+
function withHubCatalogRefs(pointer, metadataRef) {
|
|
17995
|
+
return {
|
|
17996
|
+
spicedb: { instanceRefs: [] },
|
|
17997
|
+
...pointer,
|
|
17998
|
+
opsHubMetadataRef: metadataRef,
|
|
17999
|
+
mode: typeof pointer.mode === "string" ? pointer.mode : "policy-engine"
|
|
18000
|
+
};
|
|
18001
|
+
}
|
|
18002
|
+
async function publishHubAuthzCatalog(compiled, awsConfig) {
|
|
18003
|
+
const { SSMClient: SSMClient5, GetParameterCommand: GetParameterCommand2, PutParameterCommand } = await import("@aws-sdk/client-ssm");
|
|
18004
|
+
const ssm = new SSMClient5(awsConfig);
|
|
18005
|
+
await ssm.send(
|
|
18006
|
+
new PutParameterCommand({
|
|
18007
|
+
Name: HUB_OPS_HUB_METADATA_PARAM,
|
|
18008
|
+
Type: "String",
|
|
18009
|
+
// The blob is ~7.5 KB — past the 4 KB standard tier, within Advanced (8 KB),
|
|
18010
|
+
// the tier the platform's Export uses for the same param.
|
|
18011
|
+
Tier: "Advanced",
|
|
18012
|
+
Overwrite: true,
|
|
18013
|
+
Value: JSON.stringify(compiled.metadata),
|
|
18014
|
+
Description: "Hub role catalog \u2014 ops-hub-metadata (local analog of MeshHub's SpiceDBSchema export)"
|
|
18015
|
+
})
|
|
18016
|
+
);
|
|
18017
|
+
let current = {};
|
|
18018
|
+
try {
|
|
18019
|
+
const existing = await ssm.send(new GetParameterCommand2({ Name: HUB_AUTHZ_POINTER_PARAM }));
|
|
18020
|
+
current = JSON.parse(existing.Parameter?.Value ?? "{}");
|
|
18021
|
+
} catch {
|
|
18022
|
+
}
|
|
18023
|
+
await ssm.send(
|
|
18024
|
+
new PutParameterCommand({
|
|
18025
|
+
Name: HUB_AUTHZ_POINTER_PARAM,
|
|
18026
|
+
Type: "String",
|
|
18027
|
+
Overwrite: true,
|
|
18028
|
+
Value: JSON.stringify(withHubCatalogRefs(current, HUB_OPS_HUB_METADATA_PARAM)),
|
|
18029
|
+
Description: "Hub authz pointer (local analog of the platform Pulumi program)"
|
|
18030
|
+
})
|
|
18031
|
+
);
|
|
18032
|
+
return { bundles: catalogBundleNames(compiled.metadata) };
|
|
18033
|
+
}
|
|
18034
|
+
function catalogBundleNames(metadata) {
|
|
18035
|
+
const bundles = metadata.bundles;
|
|
18036
|
+
if (Array.isArray(bundles)) {
|
|
18037
|
+
return bundles.map((b) => b && typeof b === "object" && typeof b.name === "string" ? b.name : null).filter((n) => n !== null);
|
|
18038
|
+
}
|
|
18039
|
+
if (bundles && typeof bundles === "object") return Object.keys(bundles);
|
|
18040
|
+
return [];
|
|
18041
|
+
}
|
|
18042
|
+
var HUB_AUTHZ_POINTER_PARAM, HUB_OPS_HUB_METADATA_PARAM;
|
|
18043
|
+
var init_seed_hub_catalog = __esm({
|
|
18044
|
+
"libs/mesh-cli/src/commands/local/seed-hub-catalog.ts"() {
|
|
18045
|
+
"use strict";
|
|
18046
|
+
init_seed();
|
|
18047
|
+
HUB_AUTHZ_POINTER_PARAM = `/mesh-platform/${LOCAL_TENANT}/${LOCAL_ENV}/apps/hub/stacks/local/authz`;
|
|
18048
|
+
HUB_OPS_HUB_METADATA_PARAM = `${HUB_AUTHZ_POINTER_PARAM}/ops-hub-metadata`;
|
|
17800
18049
|
}
|
|
17801
18050
|
});
|
|
17802
18051
|
|
|
@@ -17973,6 +18222,17 @@ Re-running start from here may recreate shared containers with this checkout's c
|
|
|
17973
18222
|
logInfo("Seeding Zitadel (Platform project, Mesh CLI app, Hub auth, test users)\u2026");
|
|
17974
18223
|
const zitadel = await seedZitadel(LOCAL_AWS_CONFIG);
|
|
17975
18224
|
hubAuth = zitadel.hubAuth;
|
|
18225
|
+
if (hubVersion) {
|
|
18226
|
+
const compiled = readHubCompiledAuthz(hubVersion);
|
|
18227
|
+
if (compiled) {
|
|
18228
|
+
const { bundles } = await publishHubAuthzCatalog(compiled, LOCAL_AWS_CONFIG);
|
|
18229
|
+
logSuccess(`Hub role catalog seeded: ${bundles.length} role(s) (${bundles.join(", ")})`);
|
|
18230
|
+
} else {
|
|
18231
|
+
logWarn(
|
|
18232
|
+
`Hub v${hubVersion} ships no compiled role catalog (api/dist/authz/compiled.json) \u2014 Users \u2192 Roles will be empty. Hub \u2265 2.1.0 publishes one.`
|
|
18233
|
+
);
|
|
18234
|
+
}
|
|
18235
|
+
}
|
|
17976
18236
|
try {
|
|
17977
18237
|
const reconciled = await reconcileRegistryFromZitadel();
|
|
17978
18238
|
if (reconciled.tenants.length > 0) {
|
|
@@ -18140,6 +18400,7 @@ var init_local = __esm({
|
|
|
18140
18400
|
init_auth_provision();
|
|
18141
18401
|
init_seed();
|
|
18142
18402
|
init_seed_zitadel();
|
|
18403
|
+
init_seed_hub_catalog();
|
|
18143
18404
|
WAIT_TIMEOUT_MS = 18e4;
|
|
18144
18405
|
WAIT_POLL_MS = 3e3;
|
|
18145
18406
|
}
|