forgeos 0.1.0-alpha.38 → 0.1.0-alpha.39
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 +15 -0
- package/docs/changelog.md +8 -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/main.ts +1 -1
- package/src/forge/cli/new.ts +1 -0
- package/src/forge/cli/output.ts +1 -1
- package/src/forge/cli/workos.ts +113 -18
- package/src/forge/compiler/integration/plan.ts +13 -1
- package/src/forge/compiler/integration/templates/workos.ts +2 -2
- 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.39 input=1c15364ea76cedba476c98d2f661e485cdee778fa10e7b06f7db1b6f23b1dc4a content=721818a6f9a664aa898092d4aa5cd68cbbd8ad6e150c7d3ad3ef785a2e3fcf53
|
|
2
2
|
# AGENTS.md
|
|
3
3
|
|
|
4
4
|
<!-- forge-generated:start -->
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# forgeos
|
|
2
2
|
|
|
3
|
+
## 0.1.0-alpha.39
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Harden real WorkOS seed setup for production-like field tests.
|
|
8
|
+
|
|
9
|
+
- Emit valid YAML for generated `workos-seed.yml` files by skipping the
|
|
10
|
+
JavaScript-style deterministic header on YAML artifacts.
|
|
11
|
+
- Make `forge workos seed --dry-run` the validation-only path and let
|
|
12
|
+
`forge workos seed --file workos-seed.yml --json` delegate to the
|
|
13
|
+
authenticated WorkOS CLI.
|
|
14
|
+
- Treat known WorkOS duplicate-resource seed responses such as permission
|
|
15
|
+
slugs already in use as an already-applied seed instead of a hard failure.
|
|
16
|
+
- Ignore `.workos-seed-state.json` in newly scaffolded apps.
|
|
17
|
+
|
|
3
18
|
## 0.1.0-alpha.37
|
|
4
19
|
|
|
5
20
|
### Patch Changes
|
package/docs/changelog.md
CHANGED
|
@@ -6,6 +6,14 @@ The canonical source file in the repository is `CHANGELOG.md`.
|
|
|
6
6
|
|
|
7
7
|
## Unreleased
|
|
8
8
|
|
|
9
|
+
## 0.1.0-alpha.39
|
|
10
|
+
|
|
11
|
+
- Hardened real WorkOS seed setup: generated `workos-seed.yml` files no longer
|
|
12
|
+
use `// @forge-generated` headers, `forge workos seed --dry-run` is the
|
|
13
|
+
validation-only path, real seed runs handle known duplicate-resource WorkOS
|
|
14
|
+
responses as already-applied state, and scaffolded apps ignore
|
|
15
|
+
`.workos-seed-state.json` by default.
|
|
16
|
+
|
|
9
17
|
## 0.1.0-alpha.38
|
|
10
18
|
|
|
11
19
|
- Added first-class production deployment commands:
|
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.39","releaseId":"forgeos@0.1.0-alpha.39+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.39 input=1c15364ea76cedba476c98d2f661e485cdee778fa10e7b06f7db1b6f23b1dc4a content=d76bcca47149c3378aca45191d0df75cbc6c25b49548ba94b7e2ac4ecbaa91a8
|
|
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.39",
|
|
23
|
+
"releaseId": "forgeos@0.1.0-alpha.39+unknown",
|
|
24
24
|
"schemaVersion": "0.1.0"
|
|
25
25
|
} as const;
|
package/src/forge/cli/main.ts
CHANGED
|
@@ -36,7 +36,7 @@ function formatHelp(): string {
|
|
|
36
36
|
" forge workos install --yes --json Delegate AuthKit setup to npx --yes workos@latest install",
|
|
37
37
|
" forge workos doctor --json Check WorkOS AuthKit/FGA files, claims, seed, webhook, and tenant guards",
|
|
38
38
|
" forge workos doctor --yes --json Run local checks, then delegate to npx --yes workos@latest doctor",
|
|
39
|
-
" forge workos seed --file workos-seed.yml --json
|
|
39
|
+
" forge workos seed --file workos-seed.yml --dry-run --json Validate WorkOS seed without hosted changes",
|
|
40
40
|
" forge deploy plan --target docker --json Explain production deploy gates and commands",
|
|
41
41
|
" forge deploy check --production --json Gate auth, DB, metadata, generated artifacts, and liveQuery readiness",
|
|
42
42
|
" forge deploy render docker Write Docker production deploy files under deploy/",
|
package/src/forge/cli/new.ts
CHANGED
package/src/forge/cli/output.ts
CHANGED
|
@@ -232,8 +232,8 @@ export function buildAddJson(
|
|
|
232
232
|
"forge workos install --yes --json",
|
|
233
233
|
"forge workos doctor --json",
|
|
234
234
|
"forge workos doctor --yes --json",
|
|
235
|
+
"forge workos seed --file workos-seed.yml --dry-run --json",
|
|
235
236
|
"forge workos seed --file workos-seed.yml --json",
|
|
236
|
-
"forge workos seed --file workos-seed.yml --yes --json",
|
|
237
237
|
"forge auth check --json",
|
|
238
238
|
"forge auth prove --json",
|
|
239
239
|
]
|
package/src/forge/cli/workos.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
-
import {
|
|
1
|
+
import { existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { basename, join } from "node:path";
|
|
3
4
|
import { spawnSync } from "node:child_process";
|
|
4
5
|
import { GENERATED_DIR } from "../compiler/emitter/constants.ts";
|
|
5
6
|
import { stripDeterministicHeader } from "../compiler/primitives/header.ts";
|
|
@@ -84,6 +85,14 @@ function readText(root: string, path: string): string {
|
|
|
84
85
|
return stripDeterministicHeader(readFileSync(absolute, "utf8"));
|
|
85
86
|
}
|
|
86
87
|
|
|
88
|
+
function readRawText(root: string, path: string): string {
|
|
89
|
+
const absolute = join(root, path);
|
|
90
|
+
if (!existsSync(absolute)) {
|
|
91
|
+
return "";
|
|
92
|
+
}
|
|
93
|
+
return readFileSync(absolute, "utf8");
|
|
94
|
+
}
|
|
95
|
+
|
|
87
96
|
function includesAll(haystack: string, needles: string[]): boolean {
|
|
88
97
|
return needles.every((needle) => haystack.includes(needle));
|
|
89
98
|
}
|
|
@@ -389,6 +398,54 @@ function collectWorkOSChecks(workspaceRoot: string): WorkOSCheck[] {
|
|
|
389
398
|
];
|
|
390
399
|
}
|
|
391
400
|
|
|
401
|
+
function prepareSeedFileForWorkOSCli(
|
|
402
|
+
workspaceRoot: string,
|
|
403
|
+
file: string,
|
|
404
|
+
): { file: string; sanitized: boolean; cleanup: () => void } {
|
|
405
|
+
const raw = readRawText(workspaceRoot, file);
|
|
406
|
+
const stripped = stripDeterministicHeader(raw);
|
|
407
|
+
if (!raw || raw === stripped) {
|
|
408
|
+
return { file, sanitized: false, cleanup: () => undefined };
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
const tempDir = mkdtempSync(join(tmpdir(), "forge-workos-seed-"));
|
|
412
|
+
const preparedFile = join(tempDir, basename(file));
|
|
413
|
+
writeFileSync(preparedFile, stripped, "utf8");
|
|
414
|
+
return {
|
|
415
|
+
file: preparedFile,
|
|
416
|
+
sanitized: true,
|
|
417
|
+
cleanup: () => rmSync(tempDir, { recursive: true, force: true }),
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
function isWorkOSSeedAlreadyApplied(stdout: string, stderr: string): boolean {
|
|
422
|
+
const output = `${stdout}\n${stderr}`;
|
|
423
|
+
return /(?:permission|role|resource type|organization|slug|domain)[^\n]*(?:already in use|already exists|exists already)|already in use/i
|
|
424
|
+
.test(output);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
function seedData(input: {
|
|
428
|
+
seed: WorkOSSeedSummary;
|
|
429
|
+
activePermissions: string[];
|
|
430
|
+
expectedResourceTypes: string[];
|
|
431
|
+
unusedSeedPermissions: string[];
|
|
432
|
+
dryRun?: boolean;
|
|
433
|
+
seedFileSanitized?: boolean;
|
|
434
|
+
seedAlreadyApplied?: boolean;
|
|
435
|
+
nextCommand?: string;
|
|
436
|
+
}): Record<string, unknown> {
|
|
437
|
+
return {
|
|
438
|
+
seed: input.seed,
|
|
439
|
+
activePermissions: input.activePermissions,
|
|
440
|
+
expectedResourceTypes: input.expectedResourceTypes,
|
|
441
|
+
unusedSeedPermissions: input.unusedSeedPermissions,
|
|
442
|
+
dryRun: input.dryRun ?? false,
|
|
443
|
+
seedFileSanitized: input.seedFileSanitized ?? false,
|
|
444
|
+
seedAlreadyApplied: input.seedAlreadyApplied ?? false,
|
|
445
|
+
...(input.nextCommand ? { nextCommand: input.nextCommand } : {}),
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
|
|
392
449
|
export function runWorkOSDoctorCommand(options: WorkOSCommandOptions): WorkOSCommandResult {
|
|
393
450
|
const checks = collectWorkOSChecks(options.workspaceRoot);
|
|
394
451
|
const ok = checks.every((check) => check.ok);
|
|
@@ -505,33 +562,62 @@ export function runWorkOSSeedCommand(options: WorkOSCommandOptions): WorkOSComma
|
|
|
505
562
|
checks,
|
|
506
563
|
command,
|
|
507
564
|
applied: false,
|
|
508
|
-
data: { seed, activePermissions, expectedResourceTypes, unusedSeedPermissions },
|
|
565
|
+
data: seedData({ seed, activePermissions, expectedResourceTypes, unusedSeedPermissions }),
|
|
509
566
|
exitCode: 1,
|
|
510
567
|
};
|
|
511
568
|
}
|
|
512
|
-
if (
|
|
569
|
+
if (options.dryRun) {
|
|
513
570
|
return {
|
|
514
571
|
ok: true,
|
|
515
572
|
kind: "workos-seed",
|
|
516
573
|
checks,
|
|
517
574
|
command,
|
|
518
575
|
applied: false,
|
|
519
|
-
data: {
|
|
576
|
+
data: seedData({
|
|
577
|
+
seed,
|
|
578
|
+
activePermissions,
|
|
579
|
+
expectedResourceTypes,
|
|
580
|
+
unusedSeedPermissions,
|
|
581
|
+
dryRun: true,
|
|
582
|
+
nextCommand: `forge workos seed --file ${file} --json`,
|
|
583
|
+
}),
|
|
520
584
|
exitCode: 0,
|
|
521
585
|
};
|
|
522
586
|
}
|
|
523
|
-
const
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
587
|
+
const preparedSeed = prepareSeedFileForWorkOSCli(options.workspaceRoot, seed.path);
|
|
588
|
+
const delegatedCommand = [
|
|
589
|
+
"npx",
|
|
590
|
+
"--yes",
|
|
591
|
+
"workos@latest",
|
|
592
|
+
"seed",
|
|
593
|
+
"--file",
|
|
594
|
+
preparedSeed.file,
|
|
595
|
+
];
|
|
596
|
+
try {
|
|
597
|
+
const child = runExternalCommand(delegatedCommand, options);
|
|
598
|
+
const seedAlreadyApplied =
|
|
599
|
+
child.status !== 0 && isWorkOSSeedAlreadyApplied(child.stdout, child.stderr);
|
|
600
|
+
return {
|
|
601
|
+
ok: child.status === 0 || seedAlreadyApplied,
|
|
602
|
+
kind: "workos-seed",
|
|
603
|
+
checks,
|
|
604
|
+
command: delegatedCommand,
|
|
605
|
+
applied: child.status === 0,
|
|
606
|
+
data: seedData({
|
|
607
|
+
seed,
|
|
608
|
+
activePermissions,
|
|
609
|
+
expectedResourceTypes,
|
|
610
|
+
unusedSeedPermissions,
|
|
611
|
+
seedFileSanitized: preparedSeed.sanitized,
|
|
612
|
+
seedAlreadyApplied,
|
|
613
|
+
}),
|
|
614
|
+
stdout: child.stdout,
|
|
615
|
+
stderr: child.stderr,
|
|
616
|
+
exitCode: child.status === 0 || seedAlreadyApplied ? 0 : 1,
|
|
617
|
+
};
|
|
618
|
+
} finally {
|
|
619
|
+
preparedSeed.cleanup();
|
|
620
|
+
}
|
|
535
621
|
}
|
|
536
622
|
|
|
537
623
|
export function runWorkOSCommand(options: WorkOSCommandOptions): WorkOSCommandResult {
|
|
@@ -562,7 +648,16 @@ export function formatWorkOSHuman(result: WorkOSCommandResult): string {
|
|
|
562
648
|
lines.push("external WorkOS doctor not run; pass --yes to execute the WorkOS CLI command");
|
|
563
649
|
}
|
|
564
650
|
if (result.kind === "workos-seed" && !result.applied) {
|
|
565
|
-
|
|
651
|
+
const data = result.data && typeof result.data === "object"
|
|
652
|
+
? result.data as { dryRun?: boolean; seedAlreadyApplied?: boolean; nextCommand?: string }
|
|
653
|
+
: {};
|
|
654
|
+
if (data.seedAlreadyApplied) {
|
|
655
|
+
lines.push("seed already appears applied; WorkOS reported existing resources");
|
|
656
|
+
} else if (data.dryRun) {
|
|
657
|
+
lines.push(`seed dry-run only; run ${data.nextCommand ?? "forge workos seed --file workos-seed.yml --json"} to execute the WorkOS CLI command`);
|
|
658
|
+
} else {
|
|
659
|
+
lines.push("seed not applied; inspect stdout/stderr from the WorkOS CLI command");
|
|
660
|
+
}
|
|
566
661
|
}
|
|
567
662
|
return `${lines.join("\n")}\n`;
|
|
568
663
|
}
|
|
@@ -41,11 +41,23 @@ export interface IntegrationPlanInput {
|
|
|
41
41
|
existingLock: ForgeLock | null;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
+
function headerForGeneratedPath(path: string): EmitFile["header"] {
|
|
45
|
+
return path.endsWith(".yml") || path.endsWith(".yaml") ? "none" : undefined;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function headerForRootFile(path: string): EmitFile["header"] {
|
|
49
|
+
if (path.endsWith(".env.example") || path.endsWith(".yml") || path.endsWith(".yaml")) {
|
|
50
|
+
return "none";
|
|
51
|
+
}
|
|
52
|
+
return "deterministic";
|
|
53
|
+
}
|
|
54
|
+
|
|
44
55
|
function makeEmitFile(path: string, content: string): EmitFile {
|
|
45
56
|
return {
|
|
46
57
|
path,
|
|
47
58
|
content,
|
|
48
59
|
contentHash: hashStable(content),
|
|
60
|
+
header: headerForGeneratedPath(path),
|
|
49
61
|
};
|
|
50
62
|
}
|
|
51
63
|
|
|
@@ -236,7 +248,7 @@ export function buildIntegrationEmitPlan(input: IntegrationPlanInput): EmitPlan
|
|
|
236
248
|
path: rootFile,
|
|
237
249
|
content,
|
|
238
250
|
contentHash: hashStable(content),
|
|
239
|
-
header: rootFile
|
|
251
|
+
header: headerForRootFile(rootFile),
|
|
240
252
|
});
|
|
241
253
|
}
|
|
242
254
|
|
|
@@ -184,8 +184,8 @@ export function renderWorkosAuthkit(_input: IntegrationTemplateInput): string {
|
|
|
184
184
|
' "forge workos install --yes --json",',
|
|
185
185
|
' "forge workos doctor --json",',
|
|
186
186
|
' "forge workos doctor --yes --json",',
|
|
187
|
+
' "forge workos seed --file workos-seed.yml --dry-run --json",',
|
|
187
188
|
' "forge workos seed --file workos-seed.yml --json",',
|
|
188
|
-
' "forge workos seed --file workos-seed.yml --yes --json",',
|
|
189
189
|
' "forge auth check --json",',
|
|
190
190
|
' "forge auth prove --json",',
|
|
191
191
|
"] as const;",
|
|
@@ -1077,8 +1077,8 @@ export function renderWorkosDoc(input: IntegrationTemplateInput): string {
|
|
|
1077
1077
|
"forge workos install --yes --json",
|
|
1078
1078
|
"forge workos doctor --json",
|
|
1079
1079
|
"forge workos doctor --yes --json",
|
|
1080
|
+
"forge workos seed --file workos-seed.yml --dry-run --json",
|
|
1080
1081
|
"forge workos seed --file workos-seed.yml --json",
|
|
1081
|
-
"forge workos seed --file workos-seed.yml --yes --json",
|
|
1082
1082
|
"forge auth check --json",
|
|
1083
1083
|
"forge auth prove --json",
|
|
1084
1084
|
"forge check --json",
|
package/src/forge/version.ts
CHANGED