forgeos 0.1.0-alpha.47 → 0.1.0-alpha.49
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/AGENTS.md +1 -1
- package/CHANGELOG.md +40 -0
- package/docs/changelog.md +29 -0
- package/package.json +1 -1
- package/src/forge/_generated/releaseManifest.json +1 -1
- package/src/forge/_generated/releaseManifest.ts +3 -3
- package/src/forge/cli/commands.ts +179 -1
- package/src/forge/cli/deploy.ts +34 -1
- package/src/forge/cli/main.ts +4 -0
- package/src/forge/cli/parse.ts +21 -3
- package/src/forge/cli/workos.ts +1547 -13
- package/src/forge/compiler/diagnostics/codes.ts +6 -0
- package/src/forge/compiler/integration/templates/workos.ts +22 -0
- package/src/forge/version.ts +1 -1
package/AGENTS.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// @forge-generated generator=0.1.0-alpha.
|
|
1
|
+
// @forge-generated generator=0.1.0-alpha.49 input=a7c20b1d3492dcb4111d826ff8622647abd8f6be499cffb3c27e077c9b3267b1 content=721818a6f9a664aa898092d4aa5cd68cbbd8ad6e150c7d3ad3ef785a2e3fcf53
|
|
2
2
|
# AGENTS.md
|
|
3
3
|
|
|
4
4
|
<!-- forge-generated:start -->
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,45 @@
|
|
|
1
1
|
# forgeos
|
|
2
2
|
|
|
3
|
+
## 0.1.0-alpha.49
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Improve release doctor visibility for npm dist-tag drift.
|
|
8
|
+
|
|
9
|
+
- `forge release doctor --json` now checks the published npm `alpha` and
|
|
10
|
+
`latest` dist-tags for public packages, reports the currently published
|
|
11
|
+
tag versions, and suggests the exact `npm dist-tag add` remediation when
|
|
12
|
+
`latest` still points at an older alpha.
|
|
13
|
+
- The npm dist-tag check is advisory rather than publish-blocking, so a
|
|
14
|
+
prepared alpha release can still ship while clearly reporting that the
|
|
15
|
+
consumer-facing `latest` tag needs promotion.
|
|
16
|
+
- GitHub Actions latest-promotion notices now include the local remediation
|
|
17
|
+
command and the `NPM_TOKEN` configuration hint when latest promotion is
|
|
18
|
+
skipped.
|
|
19
|
+
|
|
20
|
+
## 0.1.0-alpha.48
|
|
21
|
+
|
|
22
|
+
### Patch Changes
|
|
23
|
+
|
|
24
|
+
- Complete the WorkOS FGA bridge path for production-shaped apps.
|
|
25
|
+
|
|
26
|
+
- `forge workos fga plan/sync/prove` now models the derived app resource
|
|
27
|
+
graph, hosted WorkOS resource-type setup, required membership evidence, and
|
|
28
|
+
proof scenarios as a first-class FGA manifest.
|
|
29
|
+
- `forge workos fga sync --real` and `forge workos fga prove --real` can run
|
|
30
|
+
through the authenticated WorkOS CLI/API path when `WORKOS_API_KEY` is not
|
|
31
|
+
present, preserving the no-dashboard agent flow while still using real
|
|
32
|
+
WorkOS Authorization API calls.
|
|
33
|
+
- FGA setup guidance now distinguishes what ForgeOS can automate
|
|
34
|
+
(permissions/roles through seed, resources, checks, deploy evidence) from
|
|
35
|
+
hosted WorkOS resource-type configuration, with precise remediation when
|
|
36
|
+
resource types are missing.
|
|
37
|
+
- `forge deploy check --production` now points WorkOS apps at the full FGA
|
|
38
|
+
sequence: plan, real sync, real proof, and final deploy check.
|
|
39
|
+
- WorkOS seed generation now preserves inferred FGA parent resources such as
|
|
40
|
+
`access_request -> vendor`, and regression coverage guards the generated
|
|
41
|
+
seed, CLI parser, deploy gates, and FGA setup output.
|
|
42
|
+
|
|
3
43
|
## 0.1.0-alpha.47
|
|
4
44
|
|
|
5
45
|
### Patch Changes
|
package/docs/changelog.md
CHANGED
|
@@ -6,6 +6,35 @@ The canonical source file in the repository is `CHANGELOG.md`.
|
|
|
6
6
|
|
|
7
7
|
## Unreleased
|
|
8
8
|
|
|
9
|
+
## 0.1.0-alpha.49
|
|
10
|
+
|
|
11
|
+
- `forge release doctor --json` now reports npm `alpha` and `latest` dist-tag
|
|
12
|
+
versions for public packages, including exact remediation when `latest`
|
|
13
|
+
points at an older alpha.
|
|
14
|
+
- The dist-tag check is advisory and non-publish-blocking, keeping alpha
|
|
15
|
+
release validation usable while making stale `latest` state explicit.
|
|
16
|
+
- GitHub Actions latest-promotion notices now include the local
|
|
17
|
+
`npm dist-tag add <package>@<version> latest` fallback and `NPM_TOKEN`
|
|
18
|
+
configuration hint when promotion is skipped.
|
|
19
|
+
|
|
20
|
+
## 0.1.0-alpha.48
|
|
21
|
+
|
|
22
|
+
- Added the production-shaped WorkOS FGA flow:
|
|
23
|
+
`forge workos fga plan`, `sync`, `prove`, and `doctor` now derive an FGA
|
|
24
|
+
manifest from the app contract, policies, data graph, and `workos-seed.yml`.
|
|
25
|
+
- `forge workos fga sync --real` and `forge workos fga prove --real` can use
|
|
26
|
+
the authenticated WorkOS CLI/API path when `WORKOS_API_KEY` is not available,
|
|
27
|
+
so agents can continue the no-dashboard flow after `workos auth login`.
|
|
28
|
+
- FGA setup output now separates hosted WorkOS resource-type configuration from
|
|
29
|
+
ForgeOS-automated resource sync and authorization checks, and reports missing
|
|
30
|
+
resource types with exact next actions.
|
|
31
|
+
- `forge deploy check --production` now requires matching WorkOS FGA real
|
|
32
|
+
sync/proof evidence for WorkOS-backed apps and points operators at
|
|
33
|
+
`forge workos fga plan --write`, `sync --real`, and `prove --real`.
|
|
34
|
+
- WorkOS seed generation now keeps inferred parent resource relationships such
|
|
35
|
+
as `access_request -> vendor`, with regression coverage for the generated
|
|
36
|
+
seed, parser, deploy checks, and FGA plan output.
|
|
37
|
+
|
|
9
38
|
## 0.1.0-alpha.47
|
|
10
39
|
|
|
11
40
|
- `forge workos setup --real` and `forge workos prove --real` now validate the
|
package/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"defaultProvider":"local","diagnostics":[],"env":{"deployEnv":"FORGE_DEPLOY_ENV","deployId":"FORGE_DEPLOY_ID","publicReleaseId":"NEXT_PUBLIC_FORGE_RELEASE_ID","releaseId":"FORGE_RELEASE_ID"},"gitSha":"unknown","optionalProviders":["local","sentry-compatible","sentry","glitchtip","bugsink","otel","custom"],"packageName":"forgeos","packageVersion":"0.1.0-alpha.
|
|
1
|
+
{"defaultProvider":"local","diagnostics":[],"env":{"deployEnv":"FORGE_DEPLOY_ENV","deployId":"FORGE_DEPLOY_ID","publicReleaseId":"NEXT_PUBLIC_FORGE_RELEASE_ID","releaseId":"FORGE_RELEASE_ID"},"gitSha":"unknown","optionalProviders":["local","sentry-compatible","sentry","glitchtip","bugsink","otel","custom"],"packageName":"forgeos","packageVersion":"0.1.0-alpha.49","releaseId":"forgeos@0.1.0-alpha.49+unknown","schemaVersion":"0.1.0"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// @forge-generated generator=0.1.0-alpha.
|
|
1
|
+
// @forge-generated generator=0.1.0-alpha.49 input=a7c20b1d3492dcb4111d826ff8622647abd8f6be499cffb3c27e077c9b3267b1 content=bfe9659e5f5a5d2d52e6ac7eb6c13d2bc356c8bf95724fafc6e10fe4641f2104
|
|
2
2
|
export const releaseManifest = {
|
|
3
3
|
"defaultProvider": "local",
|
|
4
4
|
"diagnostics": [],
|
|
@@ -19,7 +19,7 @@ export const releaseManifest = {
|
|
|
19
19
|
"custom"
|
|
20
20
|
],
|
|
21
21
|
"packageName": "forgeos",
|
|
22
|
-
"packageVersion": "0.1.0-alpha.
|
|
23
|
-
"releaseId": "forgeos@0.1.0-alpha.
|
|
22
|
+
"packageVersion": "0.1.0-alpha.49",
|
|
23
|
+
"releaseId": "forgeos@0.1.0-alpha.49+unknown",
|
|
24
24
|
"schemaVersion": "0.1.0"
|
|
25
25
|
} as const;
|
|
@@ -6,6 +6,8 @@ import { classify } from "../compiler/classifier/classify.ts";
|
|
|
6
6
|
import { buildRuntimeMatrix } from "../compiler/classifier/runtime-matrix.ts";
|
|
7
7
|
import {
|
|
8
8
|
FORGE_DB_EMPTY_TIMESTAMP_LITERAL,
|
|
9
|
+
FORGE_RELEASE_NPM_DIST_TAG_CHECK_FAILED,
|
|
10
|
+
FORGE_RELEASE_NPM_DIST_TAG_STALE,
|
|
9
11
|
FORGE_RELEASE_PACKAGE_PACK_FAILED,
|
|
10
12
|
} from "../compiler/diagnostics/codes.ts";
|
|
11
13
|
import { createDiagnostic } from "../compiler/diagnostics/create.ts";
|
|
@@ -1149,7 +1151,7 @@ interface ReleaseDoctorCheck {
|
|
|
1149
1151
|
requiredForPublish: boolean;
|
|
1150
1152
|
requiredForProduction?: boolean;
|
|
1151
1153
|
state?: string;
|
|
1152
|
-
result: ReleaseCommandResult | SelfHostCommandResult | DocsCheckResult | DeployCommandResult | PackagePackCheckResult;
|
|
1154
|
+
result: ReleaseCommandResult | SelfHostCommandResult | DocsCheckResult | DeployCommandResult | PackagePackCheckResult | NpmDistTagCheckResult;
|
|
1153
1155
|
}
|
|
1154
1156
|
|
|
1155
1157
|
interface PackagePackCheckResult {
|
|
@@ -1166,6 +1168,24 @@ interface PackagePackCheckResult {
|
|
|
1166
1168
|
exitCode: 0 | 1;
|
|
1167
1169
|
}
|
|
1168
1170
|
|
|
1171
|
+
interface NpmDistTagCheckResult {
|
|
1172
|
+
ok: boolean;
|
|
1173
|
+
data: {
|
|
1174
|
+
command: string;
|
|
1175
|
+
packageName: string | null;
|
|
1176
|
+
packageVersion: string | null;
|
|
1177
|
+
alphaVersion: string | null;
|
|
1178
|
+
latestVersion: string | null;
|
|
1179
|
+
alphaMatches: boolean;
|
|
1180
|
+
latestMatches: boolean;
|
|
1181
|
+
source: "npm-view" | "env-fixture" | "skipped";
|
|
1182
|
+
};
|
|
1183
|
+
diagnostics: ReturnType<typeof createDiagnostic>[];
|
|
1184
|
+
nextActions?: string[];
|
|
1185
|
+
failureKind?: string;
|
|
1186
|
+
exitCode: 0 | 1;
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1169
1189
|
interface ReleaseDoctorResult {
|
|
1170
1190
|
schemaVersion: "0.1.0";
|
|
1171
1191
|
ok: boolean;
|
|
@@ -1263,6 +1283,156 @@ function runPackagePackDryRun(workspaceRoot: string): PackagePackCheckResult {
|
|
|
1263
1283
|
}
|
|
1264
1284
|
}
|
|
1265
1285
|
|
|
1286
|
+
function parseNpmDistTagsJson(raw: string): Record<string, string> | null {
|
|
1287
|
+
try {
|
|
1288
|
+
const parsed = JSON.parse(raw) as unknown;
|
|
1289
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
1290
|
+
return null;
|
|
1291
|
+
}
|
|
1292
|
+
const tags: Record<string, string> = {};
|
|
1293
|
+
for (const [key, value] of Object.entries(parsed)) {
|
|
1294
|
+
if (typeof value === "string") {
|
|
1295
|
+
tags[key] = value;
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
return tags;
|
|
1299
|
+
} catch {
|
|
1300
|
+
return null;
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
function runNpmDistTagCheck(workspaceRoot: string): NpmDistTagCheckResult {
|
|
1305
|
+
const pkg = readPackageJson(workspaceRoot) as {
|
|
1306
|
+
name?: unknown;
|
|
1307
|
+
version?: unknown;
|
|
1308
|
+
private?: unknown;
|
|
1309
|
+
};
|
|
1310
|
+
const packageName = typeof pkg.name === "string" ? pkg.name : null;
|
|
1311
|
+
const packageVersion = typeof pkg.version === "string" ? pkg.version : null;
|
|
1312
|
+
const command = packageName ? `npm view ${packageName} dist-tags --json` : "npm view <package> dist-tags --json";
|
|
1313
|
+
const baseData = {
|
|
1314
|
+
command,
|
|
1315
|
+
packageName,
|
|
1316
|
+
packageVersion,
|
|
1317
|
+
alphaVersion: null,
|
|
1318
|
+
latestVersion: null,
|
|
1319
|
+
alphaMatches: false,
|
|
1320
|
+
latestMatches: false,
|
|
1321
|
+
source: "skipped" as const,
|
|
1322
|
+
};
|
|
1323
|
+
|
|
1324
|
+
if (pkg.private === true) {
|
|
1325
|
+
return {
|
|
1326
|
+
ok: true,
|
|
1327
|
+
data: baseData,
|
|
1328
|
+
diagnostics: [],
|
|
1329
|
+
exitCode: 0,
|
|
1330
|
+
};
|
|
1331
|
+
}
|
|
1332
|
+
if (!packageName || !packageVersion) {
|
|
1333
|
+
return {
|
|
1334
|
+
ok: false,
|
|
1335
|
+
data: baseData,
|
|
1336
|
+
diagnostics: [
|
|
1337
|
+
createDiagnostic({
|
|
1338
|
+
severity: "warning",
|
|
1339
|
+
code: FORGE_RELEASE_NPM_DIST_TAG_CHECK_FAILED,
|
|
1340
|
+
message: "release doctor could not check npm dist-tags because package.json is missing name or version",
|
|
1341
|
+
fixHint: "Set package.json name and version before publishing, then rerun forge release doctor.",
|
|
1342
|
+
suggestedCommands: ["node bin/forge.mjs release doctor --json"],
|
|
1343
|
+
}),
|
|
1344
|
+
],
|
|
1345
|
+
nextActions: ["node bin/forge.mjs release doctor --json"],
|
|
1346
|
+
failureKind: "npm-dist-tags-package-metadata-missing",
|
|
1347
|
+
exitCode: 1,
|
|
1348
|
+
};
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
const fixture = process.env.FORGE_RELEASE_NPM_DIST_TAGS_JSON;
|
|
1352
|
+
const source = fixture ? "env-fixture" as const : "npm-view" as const;
|
|
1353
|
+
const stdout = fixture ?? spawnSync("npm", ["view", packageName, "dist-tags", "--json"], {
|
|
1354
|
+
cwd: workspaceRoot,
|
|
1355
|
+
encoding: "utf8",
|
|
1356
|
+
timeout: 10_000,
|
|
1357
|
+
windowsHide: true,
|
|
1358
|
+
}).stdout;
|
|
1359
|
+
const tags = parseNpmDistTagsJson(stdout || "");
|
|
1360
|
+
if (!tags) {
|
|
1361
|
+
return {
|
|
1362
|
+
ok: false,
|
|
1363
|
+
data: { ...baseData, source },
|
|
1364
|
+
diagnostics: [
|
|
1365
|
+
createDiagnostic({
|
|
1366
|
+
severity: "warning",
|
|
1367
|
+
code: FORGE_RELEASE_NPM_DIST_TAG_CHECK_FAILED,
|
|
1368
|
+
message: `release doctor could not read npm dist-tags for ${packageName}`,
|
|
1369
|
+
fixHint: `Run ${command}. If the package has not been published yet, publish it with the alpha tag first.`,
|
|
1370
|
+
suggestedCommands: [command, "npm run release:publish-alpha"],
|
|
1371
|
+
}),
|
|
1372
|
+
],
|
|
1373
|
+
nextActions: [command, "npm run release:publish-alpha"],
|
|
1374
|
+
failureKind: "npm-dist-tags-unavailable",
|
|
1375
|
+
exitCode: 1,
|
|
1376
|
+
};
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1379
|
+
const alphaVersion = tags.alpha ?? null;
|
|
1380
|
+
const latestVersion = tags.latest ?? null;
|
|
1381
|
+
const alphaMatches = alphaVersion === packageVersion;
|
|
1382
|
+
const latestMatches = latestVersion === packageVersion;
|
|
1383
|
+
const data = {
|
|
1384
|
+
command,
|
|
1385
|
+
packageName,
|
|
1386
|
+
packageVersion,
|
|
1387
|
+
alphaVersion,
|
|
1388
|
+
latestVersion,
|
|
1389
|
+
alphaMatches,
|
|
1390
|
+
latestMatches,
|
|
1391
|
+
source,
|
|
1392
|
+
};
|
|
1393
|
+
if (alphaMatches && latestMatches) {
|
|
1394
|
+
return {
|
|
1395
|
+
ok: true,
|
|
1396
|
+
data,
|
|
1397
|
+
diagnostics: [],
|
|
1398
|
+
exitCode: 0,
|
|
1399
|
+
};
|
|
1400
|
+
}
|
|
1401
|
+
|
|
1402
|
+
const nextActions: string[] = [];
|
|
1403
|
+
const diagnostics: ReturnType<typeof createDiagnostic>[] = [];
|
|
1404
|
+
if (!alphaMatches) {
|
|
1405
|
+
nextActions.push("npm run release:publish-alpha");
|
|
1406
|
+
diagnostics.push(createDiagnostic({
|
|
1407
|
+
severity: "warning",
|
|
1408
|
+
code: FORGE_RELEASE_NPM_DIST_TAG_STALE,
|
|
1409
|
+
message: `${packageName}@alpha points to ${alphaVersion ?? "none"}, not local version ${packageVersion}`,
|
|
1410
|
+
fixHint: "Publish the current package with the alpha tag before asking users to install forgeos@alpha.",
|
|
1411
|
+
suggestedCommands: ["npm run release:publish-alpha", command],
|
|
1412
|
+
}));
|
|
1413
|
+
}
|
|
1414
|
+
if (!latestMatches) {
|
|
1415
|
+
const promote = `npm dist-tag add ${packageName}@${packageVersion} latest`;
|
|
1416
|
+
nextActions.push(promote);
|
|
1417
|
+
nextActions.push("configure NPM_TOKEN in GitHub Actions secrets, or run npm login locally before promoting latest");
|
|
1418
|
+
diagnostics.push(createDiagnostic({
|
|
1419
|
+
severity: "warning",
|
|
1420
|
+
code: FORGE_RELEASE_NPM_DIST_TAG_STALE,
|
|
1421
|
+
message: `${packageName}@latest points to ${latestVersion ?? "none"}, not local version ${packageVersion}`,
|
|
1422
|
+
fixHint: `Promote latest after the alpha package is verified: ${promote}`,
|
|
1423
|
+
suggestedCommands: [promote, command],
|
|
1424
|
+
}));
|
|
1425
|
+
}
|
|
1426
|
+
return {
|
|
1427
|
+
ok: false,
|
|
1428
|
+
data,
|
|
1429
|
+
diagnostics,
|
|
1430
|
+
nextActions,
|
|
1431
|
+
failureKind: !alphaMatches ? "npm-alpha-dist-tag-stale" : "npm-latest-dist-tag-stale",
|
|
1432
|
+
exitCode: 1,
|
|
1433
|
+
};
|
|
1434
|
+
}
|
|
1435
|
+
|
|
1266
1436
|
export async function runReleaseDoctorCommand(command: Extract<ForgeCommand, { kind: "release" }>): Promise<ReleaseDoctorResult> {
|
|
1267
1437
|
const release = await runReleaseCommand({
|
|
1268
1438
|
...command,
|
|
@@ -1300,6 +1470,7 @@ export async function runReleaseDoctorCommand(command: Extract<ForgeCommand, { k
|
|
|
1300
1470
|
production: true,
|
|
1301
1471
|
});
|
|
1302
1472
|
const packagePack = runPackagePackDryRun(command.workspaceRoot);
|
|
1473
|
+
const npmDistTags = runNpmDistTagCheck(command.workspaceRoot);
|
|
1303
1474
|
const checks: ReleaseDoctorCheck[] = [
|
|
1304
1475
|
{
|
|
1305
1476
|
name: "release-prepared",
|
|
@@ -1340,6 +1511,13 @@ export async function runReleaseDoctorCommand(command: Extract<ForgeCommand, { k
|
|
|
1340
1511
|
requiredForPublish: true,
|
|
1341
1512
|
result: packagePack,
|
|
1342
1513
|
},
|
|
1514
|
+
{
|
|
1515
|
+
name: "npm-dist-tags",
|
|
1516
|
+
ok: npmDistTags.ok,
|
|
1517
|
+
requiredForPublish: false,
|
|
1518
|
+
state: npmDistTags.failureKind ?? (npmDistTags.data.source === "skipped" ? "skipped" : "current"),
|
|
1519
|
+
result: npmDistTags,
|
|
1520
|
+
},
|
|
1343
1521
|
];
|
|
1344
1522
|
const failed = checks.filter((check) => !check.ok).map((check) => check.name);
|
|
1345
1523
|
const notPrepared = checks
|
package/src/forge/cli/deploy.ts
CHANGED
|
@@ -853,6 +853,25 @@ async function buildChecks(options: DeployCommandOptions): Promise<DeployCommand
|
|
|
853
853
|
command: "forge workos prove --real --file workos-seed.yml --json",
|
|
854
854
|
details: seedState,
|
|
855
855
|
});
|
|
856
|
+
const workosFgaDoctor = runWorkOSCommand({
|
|
857
|
+
subcommand: "fga",
|
|
858
|
+
fgaAction: "doctor",
|
|
859
|
+
workspaceRoot: options.workspaceRoot,
|
|
860
|
+
json: true,
|
|
861
|
+
yes: false,
|
|
862
|
+
dryRun: true,
|
|
863
|
+
real: options.production,
|
|
864
|
+
});
|
|
865
|
+
checks.push({
|
|
866
|
+
name: "workos-fga-proof",
|
|
867
|
+
ok: !options.production || workosFgaDoctor.exitCode === 0,
|
|
868
|
+
severity: options.production ? "error" : "warning",
|
|
869
|
+
message: workosFgaDoctor.exitCode === 0
|
|
870
|
+
? "WorkOS FGA resource graph state matches the current app contract and seed"
|
|
871
|
+
: "WorkOS production deploy requires FGA graph sync/proof evidence matching the current app",
|
|
872
|
+
command: "forge workos fga prove --real --file workos-seed.yml --json",
|
|
873
|
+
details: workosFgaDoctor.data,
|
|
874
|
+
});
|
|
856
875
|
}
|
|
857
876
|
const tenantProofRequired = auth.requiresTenant || hasWorkOSIntegration(options.workspaceRoot);
|
|
858
877
|
const tenantProof = tenantProofRequired
|
|
@@ -928,6 +947,9 @@ async function buildChecks(options: DeployCommandOptions): Promise<DeployCommand
|
|
|
928
947
|
});
|
|
929
948
|
|
|
930
949
|
const errorFree = checks.every((check) => check.ok || check.severity === "warning");
|
|
950
|
+
const failureActions = unique(checks.flatMap((check) =>
|
|
951
|
+
!check.ok && check.command ? expandDeployFailureCommand(check.command) : []
|
|
952
|
+
));
|
|
931
953
|
return normalizeForgeCliCommandsInValue(options.workspaceRoot, {
|
|
932
954
|
schemaVersion: "0.1.0",
|
|
933
955
|
ok: errorFree,
|
|
@@ -938,11 +960,22 @@ async function buildChecks(options: DeployCommandOptions): Promise<DeployCommand
|
|
|
938
960
|
checks,
|
|
939
961
|
nextActions: errorFree
|
|
940
962
|
? ["forge deploy verify --production --url https://app.example.com --json"]
|
|
941
|
-
:
|
|
963
|
+
: failureActions,
|
|
942
964
|
exitCode: errorFree ? 0 : 1,
|
|
943
965
|
});
|
|
944
966
|
}
|
|
945
967
|
|
|
968
|
+
function expandDeployFailureCommand(command: string): string[] {
|
|
969
|
+
if (command.includes("forge workos fga prove")) {
|
|
970
|
+
return [
|
|
971
|
+
"forge workos fga plan --file workos-seed.yml --write --json",
|
|
972
|
+
"forge workos fga sync --real --file workos-seed.yml --json",
|
|
973
|
+
command,
|
|
974
|
+
];
|
|
975
|
+
}
|
|
976
|
+
return [command];
|
|
977
|
+
}
|
|
978
|
+
|
|
946
979
|
function renderDocker(options: DeployCommandOptions): DeployCommandResult {
|
|
947
980
|
const dir = join(options.workspaceRoot, "deploy");
|
|
948
981
|
nodeFileSystem.mkdirp(dir);
|
package/src/forge/cli/main.ts
CHANGED
|
@@ -43,6 +43,10 @@ function formatHelp(): string {
|
|
|
43
43
|
" forge workos seed --file workos-seed.yml --dry-run --json Validate WorkOS seed without hosted changes",
|
|
44
44
|
" forge workos prove --file workos-seed.yml --json Prove local WorkOS/AuthKit/FGA/seed readiness without dashboard changes",
|
|
45
45
|
" forge workos setup --real --file workos-seed.yml --json Apply redirect/CORS/homepage config and seed WorkOS without dashboard",
|
|
46
|
+
" forge workos fga plan --json Derive the WorkOS FGA resource graph and proof scenarios from the app",
|
|
47
|
+
" forge workos fga plan --write --json Write .forge/workos-fga-setup.md for hosted FGA resource type setup",
|
|
48
|
+
" forge workos fga sync --json Record an idempotent local FGA graph state for deploy gates",
|
|
49
|
+
" forge workos fga prove --json Prove local FGA graph, cross-tenant denials, and state freshness",
|
|
46
50
|
" forge deploy plan --target docker --json Explain production deploy gates and commands",
|
|
47
51
|
" forge deploy check --production --json Gate auth, DB, metadata, generated artifacts, and liveQuery readiness",
|
|
48
52
|
" forge deploy package --target docker Write Docker production deploy files under deploy/",
|
package/src/forge/cli/parse.ts
CHANGED
|
@@ -19,7 +19,7 @@ import type { AgentContractSubcommand } from "./agent-contract.ts";
|
|
|
19
19
|
import type { AuthSubcommand } from "./auth.ts";
|
|
20
20
|
import type { BaselineSubcommand } from "./baseline.ts";
|
|
21
21
|
import type { AuthMdSubcommand } from "./authmd.ts";
|
|
22
|
-
import type { WorkOSSubcommand } from "./workos.ts";
|
|
22
|
+
import type { WorkOSFgaAction, WorkOSSubcommand } from "./workos.ts";
|
|
23
23
|
import type { DeploySubcommand, DeployTarget } from "./deploy.ts";
|
|
24
24
|
import type { FieldTestSubcommand } from "./field-test.ts";
|
|
25
25
|
import type { SeedSubcommand } from "./seed.ts";
|
|
@@ -152,11 +152,14 @@ export type ForgeCommand =
|
|
|
152
152
|
| {
|
|
153
153
|
kind: "workos";
|
|
154
154
|
subcommand: WorkOSSubcommand;
|
|
155
|
+
fgaAction?: WorkOSFgaAction;
|
|
155
156
|
json: boolean;
|
|
156
157
|
file?: string;
|
|
157
158
|
yes: boolean;
|
|
158
159
|
dryRun: boolean;
|
|
159
160
|
real?: boolean;
|
|
161
|
+
write?: boolean;
|
|
162
|
+
writePath?: string;
|
|
160
163
|
workspaceRoot: string;
|
|
161
164
|
}
|
|
162
165
|
| {
|
|
@@ -597,7 +600,8 @@ const AUTH_SUBCOMMANDS: AuthSubcommand[] = [
|
|
|
597
600
|
];
|
|
598
601
|
const BASELINE_SUBCOMMANDS: BaselineSubcommand[] = ["create", "status"];
|
|
599
602
|
const AUTHMD_SUBCOMMANDS: AuthMdSubcommand[] = ["generate", "check"];
|
|
600
|
-
const WORKOS_SUBCOMMANDS: WorkOSSubcommand[] = ["install", "doctor", "seed", "setup", "prove"];
|
|
603
|
+
const WORKOS_SUBCOMMANDS: WorkOSSubcommand[] = ["install", "doctor", "seed", "setup", "prove", "fga"];
|
|
604
|
+
const WORKOS_FGA_ACTIONS: WorkOSFgaAction[] = ["plan", "sync", "prove", "doctor"];
|
|
601
605
|
const DEPLOY_SUBCOMMANDS: DeploySubcommand[] = ["plan", "check", "render", "package", "verify"];
|
|
602
606
|
const FIELD_TEST_SUBCOMMANDS: FieldTestSubcommand[] = ["create", "run", "report"];
|
|
603
607
|
const SECURITY_SUBCOMMANDS: SecuritySubcommand[] = ["prove"];
|
|
@@ -761,6 +765,11 @@ function parseOptionValue(args: string[], flag: string): string | undefined {
|
|
|
761
765
|
return args[index + 1];
|
|
762
766
|
}
|
|
763
767
|
|
|
768
|
+
function parseOptionalOptionValue(args: string[], flag: string): string | undefined {
|
|
769
|
+
const value = parseOptionValue(args, flag);
|
|
770
|
+
return value && !value.startsWith("--") ? value : undefined;
|
|
771
|
+
}
|
|
772
|
+
|
|
764
773
|
function parseOptionValues(args: string[], flag: string): string[] {
|
|
765
774
|
const values: string[] = [];
|
|
766
775
|
for (let index = 0; index < args.length; index++) {
|
|
@@ -1444,18 +1453,27 @@ export function parseCli(argv: string[]): ParsedCli {
|
|
|
1444
1453
|
case "workos": {
|
|
1445
1454
|
const subcommand = rest[0] as WorkOSSubcommand | undefined;
|
|
1446
1455
|
if (!subcommand || !WORKOS_SUBCOMMANDS.includes(subcommand)) {
|
|
1447
|
-
errors.push("forge workos requires subcommand: install, doctor, seed, setup, or
|
|
1456
|
+
errors.push("forge workos requires subcommand: install, doctor, seed, setup, prove, or fga");
|
|
1457
|
+
return { command: null, workspaceRoot, errors };
|
|
1458
|
+
}
|
|
1459
|
+
const fgaAction = subcommand === "fga" ? rest[1] as WorkOSFgaAction | undefined : undefined;
|
|
1460
|
+
if (subcommand === "fga" && (!fgaAction || !WORKOS_FGA_ACTIONS.includes(fgaAction))) {
|
|
1461
|
+
errors.push("forge workos fga requires action: plan, sync, prove, or doctor");
|
|
1448
1462
|
return { command: null, workspaceRoot, errors };
|
|
1449
1463
|
}
|
|
1464
|
+
const workOSWritePath = parseOptionalOptionValue(argv, "--write");
|
|
1450
1465
|
return {
|
|
1451
1466
|
command: {
|
|
1452
1467
|
kind: "workos",
|
|
1453
1468
|
subcommand,
|
|
1469
|
+
...(fgaAction ? { fgaAction } : {}),
|
|
1454
1470
|
json: parseFlag(argv, "--json"),
|
|
1455
1471
|
file: parseOptionValue(argv, "--file"),
|
|
1456
1472
|
yes: parseFlag(argv, "--yes"),
|
|
1457
1473
|
dryRun: parseFlag(argv, "--dry-run"),
|
|
1458
1474
|
real: parseFlag(argv, "--real"),
|
|
1475
|
+
write: parseFlag(argv, "--write"),
|
|
1476
|
+
...(workOSWritePath ? { writePath: workOSWritePath } : {}),
|
|
1459
1477
|
workspaceRoot,
|
|
1460
1478
|
},
|
|
1461
1479
|
workspaceRoot,
|