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 CHANGED
@@ -1,4 +1,4 @@
1
- // @forge-generated generator=0.1.0-alpha.38 input=1b961b8fed6ae0e07fb70ef025922e4a191655d9aca0f6c8617be2f88c204192 content=721818a6f9a664aa898092d4aa5cd68cbbd8ad6e150c7d3ad3ef785a2e3fcf53
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,6 +1,6 @@
1
1
  {
2
2
  "name": "forgeos",
3
- "version": "0.1.0-alpha.38",
3
+ "version": "0.1.0-alpha.39",
4
4
  "description": "Agent-native application framework and compiler for building Forge apps without a mandatory dashboard.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -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.38","releaseId":"forgeos@0.1.0-alpha.38+unknown","schemaVersion":"0.1.0"}
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.38 input=1b961b8fed6ae0e07fb70ef025922e4a191655d9aca0f6c8617be2f88c204192 content=71d83a9364c458ef2ffea0838d343e066e959d81819610519960a33118827078
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.38",
23
- "releaseId": "forgeos@0.1.0-alpha.38+unknown",
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;
@@ -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 Plan WorkOS CLI seed",
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/",
@@ -69,6 +69,7 @@ const REQUIRED_GITIGNORE_PATHS = [
69
69
  ".forge/impact/",
70
70
  ".forge/agent-adapters/",
71
71
  ".forge/studio/",
72
+ ".workos-seed-state.json",
72
73
  ] as const;
73
74
 
74
75
  const DEFAULT_FORGE_PACKAGE_SPEC = "npm:forgeos@alpha";
@@ -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
  ]
@@ -1,5 +1,6 @@
1
- import { existsSync, readFileSync } from "node:fs";
2
- import { join } from "node:path";
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 (!options.yes || options.dryRun) {
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: { seed, activePermissions, expectedResourceTypes, unusedSeedPermissions },
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 child = runExternalCommand(command, options);
524
- return {
525
- ok: child.status === 0,
526
- kind: "workos-seed",
527
- checks,
528
- command,
529
- applied: child.status === 0,
530
- data: { seed, activePermissions, expectedResourceTypes, unusedSeedPermissions },
531
- stdout: child.stdout,
532
- stderr: child.stderr,
533
- exitCode: child.status === 0 ? 0 : 1,
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
- lines.push("seed not applied; pass --yes to execute the WorkOS CLI command");
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.endsWith(".env.example") ? "none" : "deterministic",
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",
@@ -1,3 +1,3 @@
1
- export const FORGEOS_VERSION = "0.1.0-alpha.38";
1
+ export const FORGEOS_VERSION = "0.1.0-alpha.39";
2
2
  export const GENERATOR_VERSION = FORGEOS_VERSION;
3
3
  export const CLI_VERSION = FORGEOS_VERSION;