@stackwright-pro/mcp 0.2.0-alpha.61 → 0.2.0-alpha.66
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/integrity.js +7 -7
- package/dist/integrity.js.map +1 -1
- package/dist/integrity.mjs +7 -7
- package/dist/integrity.mjs.map +1 -1
- package/dist/server.js +437 -37
- package/dist/server.js.map +1 -1
- package/dist/server.mjs +445 -45
- package/dist/server.mjs.map +1 -1
- package/package.json +1 -1
package/dist/server.mjs
CHANGED
|
@@ -1678,6 +1678,14 @@ function handleSavePhaseAnswers(input) {
|
|
|
1678
1678
|
let answers;
|
|
1679
1679
|
if (input.questions && input.questions.length > 0) {
|
|
1680
1680
|
answers = answersToManifestFormat(input.rawAnswers, input.questions);
|
|
1681
|
+
if (Object.keys(answers).length === 0 && input.rawAnswers.length > 0) {
|
|
1682
|
+
answers = Object.fromEntries(
|
|
1683
|
+
input.rawAnswers.map((a) => [
|
|
1684
|
+
a.question_header,
|
|
1685
|
+
a.selected_options.length > 1 ? a.selected_options : a.selected_options[0] ?? ""
|
|
1686
|
+
])
|
|
1687
|
+
);
|
|
1688
|
+
}
|
|
1681
1689
|
} else {
|
|
1682
1690
|
answers = Object.fromEntries(
|
|
1683
1691
|
input.rawAnswers.map((a) => [a.question_header, a.selected_options[0] ?? ""])
|
|
@@ -2277,12 +2285,13 @@ var PHASE_DEPENDENCIES = {
|
|
|
2277
2285
|
// workflow states as trigger sources. Produces OpenAPI specs consumed
|
|
2278
2286
|
// by pages and dashboard for typed data wiring.
|
|
2279
2287
|
services: ["api", "workflow"],
|
|
2280
|
-
// pages/dashboard:
|
|
2281
|
-
//
|
|
2282
|
-
//
|
|
2283
|
-
//
|
|
2284
|
-
|
|
2285
|
-
|
|
2288
|
+
// pages/dashboard: depend on services for typed endpoint wiring (OpenAPI
|
|
2289
|
+
// specs) and on geo so the specialist prompt includes geo-manifest.json
|
|
2290
|
+
// — page/dashboard otters see which page slugs are already claimed and
|
|
2291
|
+
// won't attempt to overwrite them (swp-73c). 'api' is still transitive
|
|
2292
|
+
// through 'data'; auth removed — page-otter has graceful fallback.
|
|
2293
|
+
pages: ["designer", "theme", "data", "services", "geo"],
|
|
2294
|
+
dashboard: ["designer", "theme", "data", "services", "geo"],
|
|
2286
2295
|
// auth is the penultimate phase — runs after all content-producing phases
|
|
2287
2296
|
// so it can read pages, dashboard, workflow, and geo artifacts for route
|
|
2288
2297
|
// protection and RBAC wiring. Skipped upstream phases still satisfy deps
|
|
@@ -3317,7 +3326,7 @@ function registerPipelineTools(server2) {
|
|
|
3317
3326
|
|
|
3318
3327
|
// src/tools/safe-write.ts
|
|
3319
3328
|
import { z as z12 } from "zod";
|
|
3320
|
-
import { writeFileSync as writeFileSync5, existsSync as existsSync6, mkdirSync as mkdirSync5, lstatSync as lstatSync6 } from "fs";
|
|
3329
|
+
import { writeFileSync as writeFileSync5, readFileSync as readFileSync5, existsSync as existsSync6, mkdirSync as mkdirSync5, lstatSync as lstatSync6 } from "fs";
|
|
3321
3330
|
import { normalize, isAbsolute, dirname, join as join5 } from "path";
|
|
3322
3331
|
var OTTER_WRITE_ALLOWLISTS = {
|
|
3323
3332
|
"stackwright-pro-designer-otter": [
|
|
@@ -3339,6 +3348,16 @@ var OTTER_WRITE_ALLOWLISTS = {
|
|
|
3339
3348
|
prefix: ".env",
|
|
3340
3349
|
suffix: "",
|
|
3341
3350
|
description: "Dotenv files (.env, .env.local, .env.production, etc.)"
|
|
3351
|
+
},
|
|
3352
|
+
{
|
|
3353
|
+
prefix: "lib/mock-auth",
|
|
3354
|
+
suffix: ".ts",
|
|
3355
|
+
description: "Mock auth module (DEV_ONLY_MODE role updates)"
|
|
3356
|
+
},
|
|
3357
|
+
{
|
|
3358
|
+
prefix: "package.json",
|
|
3359
|
+
suffix: ".json",
|
|
3360
|
+
description: "Project package.json (DEV_ONLY_MODE script cleanup)"
|
|
3342
3361
|
}
|
|
3343
3362
|
],
|
|
3344
3363
|
"stackwright-pro-data-otter": [
|
|
@@ -3396,7 +3415,9 @@ var PROTECTED_PATH_PREFIXES = [
|
|
|
3396
3415
|
".stackwright/artifacts/signatures.json",
|
|
3397
3416
|
// artifact signature manifest
|
|
3398
3417
|
".stackwright/questions/",
|
|
3399
|
-
".stackwright/answers/"
|
|
3418
|
+
".stackwright/answers/",
|
|
3419
|
+
".stackwright/page-registry.json"
|
|
3420
|
+
// page ownership — managed by safe_write internally
|
|
3400
3421
|
];
|
|
3401
3422
|
var MAX_SAFE_WRITE_BYTES_JSON = 512 * 1024;
|
|
3402
3423
|
var MAX_SAFE_WRITE_BYTES_YAML = 256 * 1024;
|
|
@@ -3410,6 +3431,58 @@ function getMaxBytesForPath(filePath) {
|
|
|
3410
3431
|
return { limit: MAX_SAFE_WRITE_BYTES_ENV, label: "env" };
|
|
3411
3432
|
return { limit: MAX_SAFE_WRITE_BYTES_DEFAULT, label: "default" };
|
|
3412
3433
|
}
|
|
3434
|
+
var PAGE_REGISTRY_FILE = ".stackwright/page-registry.json";
|
|
3435
|
+
function extractPageSlug(filePath) {
|
|
3436
|
+
const match = normalize(filePath).match(/^pages\/(.+?)\/content\.ya?ml$/);
|
|
3437
|
+
return match?.[1] ?? null;
|
|
3438
|
+
}
|
|
3439
|
+
function extractContentTypes(yamlContent) {
|
|
3440
|
+
const typePattern = /^\s*-?\s*type:\s*(\S+)/gm;
|
|
3441
|
+
const types = [];
|
|
3442
|
+
let m;
|
|
3443
|
+
while ((m = typePattern.exec(yamlContent)) !== null) {
|
|
3444
|
+
const typeName = m[1];
|
|
3445
|
+
if (typeName && !types.includes(typeName)) {
|
|
3446
|
+
types.push(typeName);
|
|
3447
|
+
}
|
|
3448
|
+
}
|
|
3449
|
+
return types.length > 0 ? types : ["unknown"];
|
|
3450
|
+
}
|
|
3451
|
+
function readPageRegistry(cwd) {
|
|
3452
|
+
const regPath = join5(cwd, PAGE_REGISTRY_FILE);
|
|
3453
|
+
if (!existsSync6(regPath)) return {};
|
|
3454
|
+
try {
|
|
3455
|
+
const stat = lstatSync6(regPath);
|
|
3456
|
+
if (stat.isSymbolicLink()) return {};
|
|
3457
|
+
return JSON.parse(readFileSync5(regPath, "utf-8"));
|
|
3458
|
+
} catch {
|
|
3459
|
+
return {};
|
|
3460
|
+
}
|
|
3461
|
+
}
|
|
3462
|
+
function writePageRegistry(cwd, registry) {
|
|
3463
|
+
const dir = join5(cwd, ".stackwright");
|
|
3464
|
+
mkdirSync5(dir, { recursive: true });
|
|
3465
|
+
const regPath = join5(cwd, PAGE_REGISTRY_FILE);
|
|
3466
|
+
if (existsSync6(regPath)) {
|
|
3467
|
+
const stat = lstatSync6(regPath);
|
|
3468
|
+
if (stat.isSymbolicLink()) {
|
|
3469
|
+
throw new Error("Refusing to write page registry through symlink");
|
|
3470
|
+
}
|
|
3471
|
+
}
|
|
3472
|
+
writeFileSync5(regPath, JSON.stringify(registry, null, 2) + "\n", { encoding: "utf-8" });
|
|
3473
|
+
}
|
|
3474
|
+
function checkPageOwnership(cwd, slug, callerOtter) {
|
|
3475
|
+
const registry = readPageRegistry(cwd);
|
|
3476
|
+
const existing = registry[slug];
|
|
3477
|
+
if (!existing || existing.claimedBy === callerOtter) {
|
|
3478
|
+
return { allowed: true };
|
|
3479
|
+
}
|
|
3480
|
+
return {
|
|
3481
|
+
allowed: false,
|
|
3482
|
+
owner: existing.claimedBy,
|
|
3483
|
+
contentTypes: existing.contentTypes
|
|
3484
|
+
};
|
|
3485
|
+
}
|
|
3413
3486
|
function checkPathAllowed(callerOtter, filePath) {
|
|
3414
3487
|
const normalized = normalize(filePath);
|
|
3415
3488
|
if (normalized.includes("..")) {
|
|
@@ -3451,6 +3524,16 @@ function checkPathAllowed(callerOtter, filePath) {
|
|
|
3451
3524
|
continue;
|
|
3452
3525
|
}
|
|
3453
3526
|
}
|
|
3527
|
+
if (rule.prefix === "lib/mock-auth" && rule.suffix === ".ts") {
|
|
3528
|
+
if (normalized !== "lib/mock-auth.ts") {
|
|
3529
|
+
continue;
|
|
3530
|
+
}
|
|
3531
|
+
}
|
|
3532
|
+
if (rule.prefix === "package.json" && rule.suffix === ".json") {
|
|
3533
|
+
if (normalized !== "package.json") {
|
|
3534
|
+
continue;
|
|
3535
|
+
}
|
|
3536
|
+
}
|
|
3454
3537
|
return { allowed: true, rule: rule.description };
|
|
3455
3538
|
}
|
|
3456
3539
|
}
|
|
@@ -3576,11 +3659,38 @@ function handleSafeWrite(input) {
|
|
|
3576
3659
|
};
|
|
3577
3660
|
return { text: JSON.stringify(result), isError: true };
|
|
3578
3661
|
}
|
|
3662
|
+
const pageSlug = extractPageSlug(normalized);
|
|
3663
|
+
if (pageSlug) {
|
|
3664
|
+
const ownership = checkPageOwnership(cwd, pageSlug, callerOtter);
|
|
3665
|
+
if (!ownership.allowed) {
|
|
3666
|
+
const result = {
|
|
3667
|
+
success: false,
|
|
3668
|
+
error: `Page slug "${pageSlug}" is already claimed by ${ownership.owner} (content types: ${ownership.contentTypes.join(", ")}). Use a different slug or coordinate with the owning otter.`,
|
|
3669
|
+
callerOtter,
|
|
3670
|
+
attemptedPath: filePath,
|
|
3671
|
+
allowedPaths: []
|
|
3672
|
+
};
|
|
3673
|
+
return { text: JSON.stringify(result), isError: true };
|
|
3674
|
+
}
|
|
3675
|
+
}
|
|
3579
3676
|
try {
|
|
3580
3677
|
if (createDirectories) {
|
|
3581
3678
|
mkdirSync5(dirname(fullPath), { recursive: true });
|
|
3582
3679
|
}
|
|
3583
3680
|
writeFileSync5(fullPath, content, { encoding: "utf-8" });
|
|
3681
|
+
if (pageSlug) {
|
|
3682
|
+
try {
|
|
3683
|
+
const registry = readPageRegistry(cwd);
|
|
3684
|
+
registry[pageSlug] = {
|
|
3685
|
+
slug: pageSlug,
|
|
3686
|
+
claimedBy: callerOtter,
|
|
3687
|
+
contentTypes: extractContentTypes(content),
|
|
3688
|
+
writtenAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3689
|
+
};
|
|
3690
|
+
writePageRegistry(cwd, registry);
|
|
3691
|
+
} catch {
|
|
3692
|
+
}
|
|
3693
|
+
}
|
|
3584
3694
|
const result = {
|
|
3585
3695
|
success: true,
|
|
3586
3696
|
path: normalized,
|
|
@@ -3627,7 +3737,7 @@ function registerSafeWriteTools(server2) {
|
|
|
3627
3737
|
|
|
3628
3738
|
// src/tools/auth.ts
|
|
3629
3739
|
import { z as z13 } from "zod";
|
|
3630
|
-
import { readFileSync as
|
|
3740
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync6, existsSync as existsSync7, mkdirSync as mkdirSync6 } from "fs";
|
|
3631
3741
|
import { join as join6 } from "path";
|
|
3632
3742
|
function buildHierarchy(roles) {
|
|
3633
3743
|
const h = {};
|
|
@@ -3847,10 +3957,232 @@ ${routeLines}
|
|
|
3847
3957
|
${auditSection}
|
|
3848
3958
|
`;
|
|
3849
3959
|
}
|
|
3960
|
+
function generateDevOnlyMiddlewareContent(method, params, roles, defaultRole, hierarchy, auditEnabled, auditRetentionDays, protectedRoutes) {
|
|
3961
|
+
const rbacBlock = ` rbac: {
|
|
3962
|
+
roles: ${JSON.stringify(roles)},
|
|
3963
|
+
defaultRole: '${defaultRole}',
|
|
3964
|
+
hierarchy: ${JSON.stringify(hierarchy, null, 4)},
|
|
3965
|
+
},`;
|
|
3966
|
+
const auditBlock = ` audit: {
|
|
3967
|
+
enabled: ${auditEnabled},
|
|
3968
|
+
retentionDays: ${auditRetentionDays},
|
|
3969
|
+
},`;
|
|
3970
|
+
const routesBlock = ` protectedRoutes: ${JSON.stringify(protectedRoutes)},`;
|
|
3971
|
+
const configBlock = `export const config = {
|
|
3972
|
+
matcher: ${JSON.stringify(protectedRoutes)},
|
|
3973
|
+
};`;
|
|
3974
|
+
const devHeader = [
|
|
3975
|
+
"// middleware.ts \u2014 generated by @stackwright-pro/auth-nextjs (dev-only mock)",
|
|
3976
|
+
"// DEV ONLY \u2014 uses mock authentication from lib/mock-auth.ts",
|
|
3977
|
+
"// Do NOT deploy to production.",
|
|
3978
|
+
"import { createProMiddleware } from '@stackwright-pro/auth-nextjs';",
|
|
3979
|
+
"import { mockAuthProvider } from './lib/mock-auth';"
|
|
3980
|
+
].join("\n");
|
|
3981
|
+
if (method === "cac") {
|
|
3982
|
+
const caBundle = params.cacCaBundle ?? "./certs/dod-ca-bundle.pem";
|
|
3983
|
+
const edipiLookup = params.cacEdipiLookup ?? "./config/edipi-lookup.json";
|
|
3984
|
+
const ocspEndpoint = params.cacOcspEndpoint ?? "https://ocsp.disa.mil";
|
|
3985
|
+
const certHeader = params.cacCertHeader ?? "X-SSL-Client-Cert";
|
|
3986
|
+
return `${devHeader}
|
|
3987
|
+
|
|
3988
|
+
export const middleware = createProMiddleware({
|
|
3989
|
+
method: 'cac',
|
|
3990
|
+
cac: {
|
|
3991
|
+
caBundle: '${caBundle}',
|
|
3992
|
+
edipiLookup: '${edipiLookup}',
|
|
3993
|
+
ocspEndpoint: '${ocspEndpoint}',
|
|
3994
|
+
certHeader: '${certHeader}',
|
|
3995
|
+
provider: mockAuthProvider,
|
|
3996
|
+
},
|
|
3997
|
+
${rbacBlock}
|
|
3998
|
+
${auditBlock}
|
|
3999
|
+
${routesBlock}
|
|
4000
|
+
});
|
|
4001
|
+
|
|
4002
|
+
${configBlock}
|
|
4003
|
+
`;
|
|
4004
|
+
}
|
|
4005
|
+
if (method === "oidc") {
|
|
4006
|
+
const scopes2 = params.oidcScopes ?? "openid profile email";
|
|
4007
|
+
const roleClaim = params.oidcRoleClaim ?? "roles";
|
|
4008
|
+
return `${devHeader}
|
|
4009
|
+
|
|
4010
|
+
export const middleware = createProMiddleware({
|
|
4011
|
+
method: 'oidc',
|
|
4012
|
+
oidc: {
|
|
4013
|
+
discoveryUrl: 'https://dev-mock-oidc/.well-known/openid-configuration',
|
|
4014
|
+
clientId: 'dev-mock-client',
|
|
4015
|
+
clientSecret: 'dev-mock-secret',
|
|
4016
|
+
scopes: '${scopes2}',
|
|
4017
|
+
roleClaim: '${roleClaim}',
|
|
4018
|
+
provider: mockAuthProvider,
|
|
4019
|
+
},
|
|
4020
|
+
${rbacBlock}
|
|
4021
|
+
${auditBlock}
|
|
4022
|
+
${routesBlock}
|
|
4023
|
+
});
|
|
4024
|
+
|
|
4025
|
+
${configBlock}
|
|
4026
|
+
`;
|
|
4027
|
+
}
|
|
4028
|
+
const scopes = params.oauth2Scopes ?? "read write";
|
|
4029
|
+
return `${devHeader}
|
|
4030
|
+
|
|
4031
|
+
export const middleware = createProMiddleware({
|
|
4032
|
+
method: 'oauth2',
|
|
4033
|
+
oauth2: {
|
|
4034
|
+
authorizationUrl: 'https://dev-mock-oauth2/authorize',
|
|
4035
|
+
tokenUrl: 'https://dev-mock-oauth2/token',
|
|
4036
|
+
clientId: 'dev-mock-client',
|
|
4037
|
+
clientSecret: 'dev-mock-secret',
|
|
4038
|
+
scopes: '${scopes}',
|
|
4039
|
+
provider: mockAuthProvider,
|
|
4040
|
+
},
|
|
4041
|
+
${rbacBlock}
|
|
4042
|
+
${auditBlock}
|
|
4043
|
+
${routesBlock}
|
|
4044
|
+
});
|
|
4045
|
+
|
|
4046
|
+
${configBlock}
|
|
4047
|
+
`;
|
|
4048
|
+
}
|
|
4049
|
+
function generateDevOnlyYamlBlock(method, params, roles, defaultRole, hierarchy, auditEnabled, auditRetentionDays, protectedRoutes) {
|
|
4050
|
+
const rbacSection = ` rbac:
|
|
4051
|
+
roles:
|
|
4052
|
+
${rolesToYaml(roles, " ")}
|
|
4053
|
+
defaultRole: ${defaultRole}
|
|
4054
|
+
hierarchy:
|
|
4055
|
+
${hierarchyToYaml(hierarchy, " ")}`;
|
|
4056
|
+
const auditSection = ` audit:
|
|
4057
|
+
enabled: ${auditEnabled}
|
|
4058
|
+
retentionDays: ${auditRetentionDays}`;
|
|
4059
|
+
const routeLines = protectedRoutes.map((r) => ` - pattern: ${r}
|
|
4060
|
+
requiredRole: ${defaultRole}`).join("\n");
|
|
4061
|
+
const providerLine = params.provider ? ` provider: ${params.provider}
|
|
4062
|
+
` : "";
|
|
4063
|
+
if (method === "cac") {
|
|
4064
|
+
const caBundle = params.cacCaBundle ?? "./certs/dod-ca-bundle.pem";
|
|
4065
|
+
const edipiLookup = params.cacEdipiLookup ?? "./config/edipi-lookup.json";
|
|
4066
|
+
const ocspEndpoint = params.cacOcspEndpoint ?? "https://ocsp.disa.mil";
|
|
4067
|
+
const certHeader = params.cacCertHeader ?? "X-SSL-Client-Cert";
|
|
4068
|
+
return `auth:
|
|
4069
|
+
method: cac
|
|
4070
|
+
devOnly: true
|
|
4071
|
+
${providerLine} middleware: ./middleware.ts
|
|
4072
|
+
cac:
|
|
4073
|
+
caBundle: ${caBundle}
|
|
4074
|
+
edipiLookup: ${edipiLookup}
|
|
4075
|
+
ocspEndpoint: ${ocspEndpoint}
|
|
4076
|
+
certHeader: ${certHeader}
|
|
4077
|
+
${rbacSection}
|
|
4078
|
+
protectedRoutes:
|
|
4079
|
+
${routeLines}
|
|
4080
|
+
${auditSection}
|
|
4081
|
+
`;
|
|
4082
|
+
}
|
|
4083
|
+
if (method === "oidc") {
|
|
4084
|
+
const scopes2 = params.oidcScopes ?? "openid profile email";
|
|
4085
|
+
const roleClaim = params.oidcRoleClaim ?? "roles";
|
|
4086
|
+
return `auth:
|
|
4087
|
+
method: oidc
|
|
4088
|
+
devOnly: true
|
|
4089
|
+
${providerLine} middleware: ./middleware.ts
|
|
4090
|
+
oidc:
|
|
4091
|
+
discoveryUrl: https://dev-mock-oidc/.well-known/openid-configuration
|
|
4092
|
+
clientId: dev-mock-client
|
|
4093
|
+
clientSecret: dev-mock-secret
|
|
4094
|
+
scopes: ${scopes2}
|
|
4095
|
+
roleClaim: ${roleClaim}
|
|
4096
|
+
${rbacSection}
|
|
4097
|
+
protectedRoutes:
|
|
4098
|
+
${routeLines}
|
|
4099
|
+
${auditSection}
|
|
4100
|
+
`;
|
|
4101
|
+
}
|
|
4102
|
+
const scopes = params.oauth2Scopes ?? "read write";
|
|
4103
|
+
return `auth:
|
|
4104
|
+
method: oauth2
|
|
4105
|
+
devOnly: true
|
|
4106
|
+
${providerLine} middleware: ./middleware.ts
|
|
4107
|
+
oauth2:
|
|
4108
|
+
authorizationUrl: https://dev-mock-oauth2/authorize
|
|
4109
|
+
tokenUrl: https://dev-mock-oauth2/token
|
|
4110
|
+
clientId: dev-mock-client
|
|
4111
|
+
clientSecret: dev-mock-secret
|
|
4112
|
+
scopes: ${scopes}
|
|
4113
|
+
${rbacSection}
|
|
4114
|
+
protectedRoutes:
|
|
4115
|
+
${routeLines}
|
|
4116
|
+
${auditSection}
|
|
4117
|
+
`;
|
|
4118
|
+
}
|
|
4119
|
+
function deriveDevKey(roleName) {
|
|
4120
|
+
const segment = roleName.split("_")[0];
|
|
4121
|
+
return (segment ?? roleName).toLowerCase();
|
|
4122
|
+
}
|
|
4123
|
+
function generateMockAuthContent(roles, mockUsers) {
|
|
4124
|
+
const entries = [];
|
|
4125
|
+
const scriptLines = [];
|
|
4126
|
+
for (let i = 0; i < roles.length; i++) {
|
|
4127
|
+
const role = roles[i];
|
|
4128
|
+
const devKey = deriveDevKey(role);
|
|
4129
|
+
const persona = mockUsers?.[i];
|
|
4130
|
+
const name = persona?.name ?? `Dev ${role}`;
|
|
4131
|
+
const email = persona?.email ?? `dev-${devKey}@example.mil`;
|
|
4132
|
+
const edipi = String(i + 1).padStart(10, "0");
|
|
4133
|
+
entries.push(` ${devKey}: {
|
|
4134
|
+
id: 'mock-${devKey}-${String(i + 1).padStart(3, "0")}',
|
|
4135
|
+
name: '${name}',
|
|
4136
|
+
email: '${email}',
|
|
4137
|
+
roles: ['${role}'],
|
|
4138
|
+
edipi: '${edipi}',
|
|
4139
|
+
}`);
|
|
4140
|
+
scriptLines.push(` * pnpm dev:${devKey} \u2014 ${name}`);
|
|
4141
|
+
}
|
|
4142
|
+
return `/**
|
|
4143
|
+
* Mock authentication for development mode.
|
|
4144
|
+
*
|
|
4145
|
+
* DEV_ONLY_MODE \u2014 no real auth provider. Select a persona via MOCK_USER env var
|
|
4146
|
+
* or use the convenience scripts in package.json:
|
|
4147
|
+
${scriptLines.join("\n")}
|
|
4148
|
+
*/
|
|
4149
|
+
|
|
4150
|
+
export const MOCK_USERS = {
|
|
4151
|
+
${entries.join(",\n")}
|
|
4152
|
+
} as const;
|
|
4153
|
+
|
|
4154
|
+
export type MockRole = keyof typeof MOCK_USERS;
|
|
4155
|
+
|
|
4156
|
+
export function getMockUser(role?: string) {
|
|
4157
|
+
const key = (role ?? process.env.MOCK_USER) as MockRole | undefined;
|
|
4158
|
+
if (!key) return null;
|
|
4159
|
+
return MOCK_USERS[key] ?? null;
|
|
4160
|
+
}
|
|
4161
|
+
|
|
4162
|
+
export function mockAuthProvider() {
|
|
4163
|
+
return getMockUser();
|
|
4164
|
+
}
|
|
4165
|
+
`;
|
|
4166
|
+
}
|
|
4167
|
+
function updatePackageJsonScripts(existingJson, roles, mockUsers) {
|
|
4168
|
+
const pkg = JSON.parse(existingJson);
|
|
4169
|
+
const scripts = pkg.scripts ?? {};
|
|
4170
|
+
delete scripts["dev:admin"];
|
|
4171
|
+
delete scripts["dev:analyst"];
|
|
4172
|
+
delete scripts["dev:viewer"];
|
|
4173
|
+
for (let i = 0; i < roles.length; i++) {
|
|
4174
|
+
const devKey = deriveDevKey(roles[i]);
|
|
4175
|
+
scripts[`dev:${devKey}`] = `MOCK_USER=${devKey} next dev`;
|
|
4176
|
+
}
|
|
4177
|
+
pkg.scripts = scripts;
|
|
4178
|
+
return JSON.stringify(pkg, null, 2) + "\n";
|
|
4179
|
+
}
|
|
3850
4180
|
async function configureAuthHandler(params, cwd) {
|
|
3851
4181
|
const {
|
|
3852
4182
|
method,
|
|
3853
4183
|
provider,
|
|
4184
|
+
devOnly = false,
|
|
4185
|
+
mockUsers,
|
|
3854
4186
|
rbacRoles = ["SUPER_ADMIN", "ADMIN", "ANALYST"],
|
|
3855
4187
|
auditEnabled = true,
|
|
3856
4188
|
auditRetentionDays = 90,
|
|
@@ -3880,7 +4212,16 @@ async function configureAuthHandler(params, cwd) {
|
|
|
3880
4212
|
}
|
|
3881
4213
|
const filesWritten = [];
|
|
3882
4214
|
try {
|
|
3883
|
-
const middlewareContent =
|
|
4215
|
+
const middlewareContent = devOnly ? generateDevOnlyMiddlewareContent(
|
|
4216
|
+
method,
|
|
4217
|
+
params,
|
|
4218
|
+
roles,
|
|
4219
|
+
defaultRole,
|
|
4220
|
+
hierarchy,
|
|
4221
|
+
auditEnabled,
|
|
4222
|
+
auditRetentionDays,
|
|
4223
|
+
protectedRoutes
|
|
4224
|
+
) : generateMiddlewareContent(
|
|
3884
4225
|
method,
|
|
3885
4226
|
params,
|
|
3886
4227
|
roles,
|
|
@@ -3904,30 +4245,87 @@ async function configureAuthHandler(params, cwd) {
|
|
|
3904
4245
|
isError: true
|
|
3905
4246
|
};
|
|
3906
4247
|
}
|
|
3907
|
-
|
|
3908
|
-
|
|
3909
|
-
|
|
3910
|
-
|
|
3911
|
-
|
|
3912
|
-
|
|
3913
|
-
|
|
3914
|
-
|
|
4248
|
+
if (!devOnly) {
|
|
4249
|
+
try {
|
|
4250
|
+
const envBlock = generateEnvBlock(method, params);
|
|
4251
|
+
const envPath = join6(cwd, ".env.example");
|
|
4252
|
+
if (existsSync7(envPath)) {
|
|
4253
|
+
const existing = readFileSync6(envPath, "utf8");
|
|
4254
|
+
writeFileSync6(envPath, existing.trimEnd() + "\n\n" + envBlock, "utf8");
|
|
4255
|
+
} else {
|
|
4256
|
+
writeFileSync6(envPath, envBlock, "utf8");
|
|
4257
|
+
}
|
|
4258
|
+
filesWritten.push(".env.example");
|
|
4259
|
+
} catch (err) {
|
|
4260
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
4261
|
+
return {
|
|
4262
|
+
content: [
|
|
4263
|
+
{
|
|
4264
|
+
type: "text",
|
|
4265
|
+
text: JSON.stringify({ success: false, error: `Failed writing .env.example: ${msg}` })
|
|
4266
|
+
}
|
|
4267
|
+
],
|
|
4268
|
+
isError: true
|
|
4269
|
+
};
|
|
4270
|
+
}
|
|
4271
|
+
}
|
|
4272
|
+
if (devOnly) {
|
|
4273
|
+
try {
|
|
4274
|
+
const mockAuthDir = join6(cwd, "lib");
|
|
4275
|
+
mkdirSync6(mockAuthDir, { recursive: true });
|
|
4276
|
+
const mockAuthContent = generateMockAuthContent(roles, mockUsers);
|
|
4277
|
+
writeFileSync6(join6(mockAuthDir, "mock-auth.ts"), mockAuthContent, "utf8");
|
|
4278
|
+
filesWritten.push("lib/mock-auth.ts");
|
|
4279
|
+
} catch (err) {
|
|
4280
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
4281
|
+
return {
|
|
4282
|
+
content: [
|
|
4283
|
+
{
|
|
4284
|
+
type: "text",
|
|
4285
|
+
text: JSON.stringify({
|
|
4286
|
+
success: false,
|
|
4287
|
+
error: `Failed writing lib/mock-auth.ts: ${msg}`
|
|
4288
|
+
})
|
|
4289
|
+
}
|
|
4290
|
+
],
|
|
4291
|
+
isError: true
|
|
4292
|
+
};
|
|
4293
|
+
}
|
|
4294
|
+
try {
|
|
4295
|
+
const pkgPath = join6(cwd, "package.json");
|
|
4296
|
+
if (existsSync7(pkgPath)) {
|
|
4297
|
+
const existingPkg = readFileSync6(pkgPath, "utf8");
|
|
4298
|
+
const updatedPkg = updatePackageJsonScripts(existingPkg, roles, mockUsers);
|
|
4299
|
+
writeFileSync6(pkgPath, updatedPkg, "utf8");
|
|
4300
|
+
filesWritten.push("package.json");
|
|
4301
|
+
}
|
|
4302
|
+
} catch (err) {
|
|
4303
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
4304
|
+
return {
|
|
4305
|
+
content: [
|
|
4306
|
+
{
|
|
4307
|
+
type: "text",
|
|
4308
|
+
text: JSON.stringify({
|
|
4309
|
+
success: false,
|
|
4310
|
+
error: `Failed updating package.json: ${msg}`
|
|
4311
|
+
})
|
|
4312
|
+
}
|
|
4313
|
+
],
|
|
4314
|
+
isError: true
|
|
4315
|
+
};
|
|
3915
4316
|
}
|
|
3916
|
-
filesWritten.push(".env.example");
|
|
3917
|
-
} catch (err) {
|
|
3918
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
3919
|
-
return {
|
|
3920
|
-
content: [
|
|
3921
|
-
{
|
|
3922
|
-
type: "text",
|
|
3923
|
-
text: JSON.stringify({ success: false, error: `Failed writing .env.example: ${msg}` })
|
|
3924
|
-
}
|
|
3925
|
-
],
|
|
3926
|
-
isError: true
|
|
3927
|
-
};
|
|
3928
4317
|
}
|
|
3929
4318
|
try {
|
|
3930
|
-
const authYaml =
|
|
4319
|
+
const authYaml = devOnly ? generateDevOnlyYamlBlock(
|
|
4320
|
+
method,
|
|
4321
|
+
params,
|
|
4322
|
+
roles,
|
|
4323
|
+
defaultRole,
|
|
4324
|
+
hierarchy,
|
|
4325
|
+
auditEnabled,
|
|
4326
|
+
auditRetentionDays,
|
|
4327
|
+
protectedRoutes
|
|
4328
|
+
) : generateYamlBlock(
|
|
3931
4329
|
method,
|
|
3932
4330
|
params,
|
|
3933
4331
|
roles,
|
|
@@ -3941,7 +4339,7 @@ async function configureAuthHandler(params, cwd) {
|
|
|
3941
4339
|
if (!existsSync7(ymlPath)) {
|
|
3942
4340
|
writeFileSync6(ymlPath, authYaml, "utf8");
|
|
3943
4341
|
} else {
|
|
3944
|
-
const existing =
|
|
4342
|
+
const existing = readFileSync6(ymlPath, "utf8");
|
|
3945
4343
|
writeFileSync6(ymlPath, upsertAuthBlock(existing, authYaml), "utf8");
|
|
3946
4344
|
}
|
|
3947
4345
|
filesWritten.push("stackwright.yml");
|
|
@@ -4009,7 +4407,9 @@ function registerAuthTools(server2) {
|
|
|
4009
4407
|
// Routes
|
|
4010
4408
|
protectedRoutes: jsonCoerce(z13.array(z13.string()).optional()),
|
|
4011
4409
|
// Injection for tests
|
|
4012
|
-
_cwd: z13.string().optional()
|
|
4410
|
+
_cwd: z13.string().optional(),
|
|
4411
|
+
devOnly: boolCoerce(z13.boolean().optional()),
|
|
4412
|
+
mockUsers: jsonCoerce(z13.array(z13.object({ name: z13.string(), email: z13.string() })).optional())
|
|
4013
4413
|
},
|
|
4014
4414
|
async (params) => {
|
|
4015
4415
|
const cwd = params._cwd ?? process.cwd();
|
|
@@ -4020,7 +4420,7 @@ function registerAuthTools(server2) {
|
|
|
4020
4420
|
|
|
4021
4421
|
// src/integrity.ts
|
|
4022
4422
|
import { createHash as createHash4, timingSafeEqual as timingSafeEqual2 } from "crypto";
|
|
4023
|
-
import { readFileSync as
|
|
4423
|
+
import { readFileSync as readFileSync7, readdirSync as readdirSync2, lstatSync as lstatSync7 } from "fs";
|
|
4024
4424
|
import { join as join7, basename } from "path";
|
|
4025
4425
|
var _checksums = /* @__PURE__ */ new Map([
|
|
4026
4426
|
[
|
|
@@ -4029,15 +4429,15 @@ var _checksums = /* @__PURE__ */ new Map([
|
|
|
4029
4429
|
],
|
|
4030
4430
|
[
|
|
4031
4431
|
"stackwright-pro-auth-otter.json",
|
|
4032
|
-
"
|
|
4432
|
+
"4bf6beba7150d08c74c5f6fbbeb20e988aba52a2029ff2892615e71f6ab12ed1"
|
|
4033
4433
|
],
|
|
4034
4434
|
[
|
|
4035
4435
|
"stackwright-pro-dashboard-otter.json",
|
|
4036
|
-
"
|
|
4436
|
+
"9c319d311801730e8dc9bc142eebb8fc5a7f48da48fa0b8d8c3b7431652447be"
|
|
4037
4437
|
],
|
|
4038
4438
|
[
|
|
4039
4439
|
"stackwright-pro-data-otter.json",
|
|
4040
|
-
"
|
|
4440
|
+
"4d9369277685a4acc484116920c9622ad8a1838012a493fcfe42a6ae5abe53cf"
|
|
4041
4441
|
],
|
|
4042
4442
|
[
|
|
4043
4443
|
"stackwright-pro-designer-otter.json",
|
|
@@ -4045,7 +4445,7 @@ var _checksums = /* @__PURE__ */ new Map([
|
|
|
4045
4445
|
],
|
|
4046
4446
|
[
|
|
4047
4447
|
"stackwright-pro-domain-expert-otter.json",
|
|
4048
|
-
"
|
|
4448
|
+
"6055a2efc78f54a8393f628839e2a2563bf0c6de3ad32de00c82779a53381efd"
|
|
4049
4449
|
],
|
|
4050
4450
|
[
|
|
4051
4451
|
"stackwright-pro-foreman-otter.json",
|
|
@@ -4053,15 +4453,15 @@ var _checksums = /* @__PURE__ */ new Map([
|
|
|
4053
4453
|
],
|
|
4054
4454
|
[
|
|
4055
4455
|
"stackwright-pro-geo-otter.json",
|
|
4056
|
-
"
|
|
4456
|
+
"9e09aaf2bb10197c6d1c05d0fd5f5f9380acc0cb697a410fcae839ffba648561"
|
|
4057
4457
|
],
|
|
4058
4458
|
[
|
|
4059
4459
|
"stackwright-pro-page-otter.json",
|
|
4060
|
-
"
|
|
4460
|
+
"532bb7e9a25a5c832edd1ff1ea0886dd4453905d86e6f9331eb957ae5e121833"
|
|
4061
4461
|
],
|
|
4062
4462
|
[
|
|
4063
4463
|
"stackwright-pro-polish-otter.json",
|
|
4064
|
-
"
|
|
4464
|
+
"8f284d4d6a204137cd786824fc584d5bddac1bc757204769b99ca5412cf2cea2"
|
|
4065
4465
|
],
|
|
4066
4466
|
[
|
|
4067
4467
|
"stackwright-pro-theme-otter.json",
|
|
@@ -4120,7 +4520,7 @@ function verifyOtterFile(filePath) {
|
|
|
4120
4520
|
}
|
|
4121
4521
|
let raw;
|
|
4122
4522
|
try {
|
|
4123
|
-
raw =
|
|
4523
|
+
raw = readFileSync7(filePath);
|
|
4124
4524
|
} catch (err) {
|
|
4125
4525
|
const msg = err instanceof Error ? err.message : String(err);
|
|
4126
4526
|
return { verified: false, filename, error: `Cannot read file: ${msg}` };
|
|
@@ -4290,7 +4690,7 @@ function registerIntegrityTools(server2) {
|
|
|
4290
4690
|
|
|
4291
4691
|
// src/tools/domain.ts
|
|
4292
4692
|
import { z as z14 } from "zod";
|
|
4293
|
-
import { readFileSync as
|
|
4693
|
+
import { readFileSync as readFileSync8, existsSync as existsSync8 } from "fs";
|
|
4294
4694
|
import { join as join8 } from "path";
|
|
4295
4695
|
function handleListCollections(input) {
|
|
4296
4696
|
const cwd = input._cwd ?? process.cwd();
|
|
@@ -4315,7 +4715,7 @@ function handleListCollections(input) {
|
|
|
4315
4715
|
for (const { path: path3, source, parse } of sources) {
|
|
4316
4716
|
if (!existsSync8(path3)) continue;
|
|
4317
4717
|
try {
|
|
4318
|
-
const collections = parse(
|
|
4718
|
+
const collections = parse(readFileSync8(path3, "utf8"));
|
|
4319
4719
|
return {
|
|
4320
4720
|
text: JSON.stringify({ collections, source, collectionCount: collections.length }),
|
|
4321
4721
|
isError: false
|
|
@@ -4476,7 +4876,7 @@ function handleValidateWorkflow(input) {
|
|
|
4476
4876
|
]);
|
|
4477
4877
|
}
|
|
4478
4878
|
try {
|
|
4479
|
-
raw = JSON.parse(
|
|
4879
|
+
raw = JSON.parse(readFileSync8(artifactPath, "utf8"));
|
|
4480
4880
|
} catch (err) {
|
|
4481
4881
|
return fail([{ code: "INVALID_JSON", message: `Failed to parse workflow artifact: ${err}` }]);
|
|
4482
4882
|
}
|