@getstrata/cli 2.2.36 → 2.2.38

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/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # @getstrata/cli changelog
2
2
 
3
+ ## 2.2.38
4
+
5
+ Qualify packed OIDC and SAML callbacks across Postgres, SQLite and MySQL. Fix explicitly non-migrating local bootstrap provisioning, dialect-safe transactional SAML replay, verified bearer ACS recipient binding, and independent SAML Response Destination validation through a maintained parser. Fix owned listener/client address mismatches with typed generated native options and optional CLI HOST, retaining default production binding. Preserve existing generated auth, production guards, compiler/runtime assertions and coverage gates; live provider and deployment qualification remain separate.
6
+
7
+ ## 2.2.37
8
+
9
+ Fix MySQL repository entity-returning mutations with transaction-pinned readback, persisted keys/defaults, honest null and deletion outcomes, complete model event payloads and injected connection ownership. Preserve native Postgres/SQLite RETURNING and conditional affected-row metadata. Close owned generated OpenAPI and failed-job command applications on success and failure before exit; retain both command and cleanup errors. Add mandatory packaged MySQL production column-tenancy CLI and HTTP auth/SCIM acceptance with an owned DML-only account, unchanged route/spec/SDK contracts, native ORM regressions and cleanup tests. Records dogfood issues #232 and #233 without application SQL workarounds.
10
+
3
11
  ## 2.2.36
4
12
 
5
13
  Add a mandatory installed-package SQLite column-tenancy CLI/OpenAPI/HTTP profile, including cookie/token/JWT/Basic and repeated SCIM isolation, while preserving existing Postgres security, route inventories, compiler combinations and Docker gates. Reconcile observability/rotation trackers against shipped capabilities and remaining operational qualification.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getstrata/cli",
3
- "version": "2.2.36",
3
+ "version": "2.2.38",
4
4
  "description": "Strata CLI: framework commands for Bun apps (dev, start, migrate, run)",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -54,8 +54,8 @@
54
54
  ],
55
55
  "scripts": {},
56
56
  "peerDependencies": {
57
- "@getstrata/bootstrap": "^2.2.36",
58
- "@getstrata/core": "^2.2.36"
57
+ "@getstrata/bootstrap": "^2.2.38",
58
+ "@getstrata/core": "^2.2.38"
59
59
  },
60
60
  "publishConfig": {
61
61
  "access": "public"
@@ -0,0 +1,25 @@
1
+ /** One-shot commands own only the cleanup explicitly supplied by their application. */
2
+ async function withCommandCleanup<T>(
3
+ operation: () => Promise<T>,
4
+ close?: () => void | Promise<void>,
5
+ ): Promise<T> {
6
+ let result: { ok: true; value: T } | { ok: false; error: unknown };
7
+ try {
8
+ result = { ok: true, value: await operation() };
9
+ } catch (error) {
10
+ result = { ok: false, error };
11
+ }
12
+ try {
13
+ await close?.();
14
+ } catch (error) {
15
+ if (!result.ok)
16
+ throw new AggregateError([result.error, error], "Command and application cleanup failed.", {
17
+ cause: result.error,
18
+ });
19
+ throw error;
20
+ }
21
+ if (!result.ok) throw result.error;
22
+ return result.value;
23
+ }
24
+
25
+ export { withCommandCleanup };
package/src/openapi.ts CHANGED
@@ -6,6 +6,9 @@ import { generateOpenApiSpec, renderOpenApiDocument } from "@getstrata/core/open
6
6
  import { validateOpenApiSpec } from "@getstrata/core/openapi/validate";
7
7
  import { readSpaPrefix } from "@getstrata/core/runtime/frontendMode";
8
8
 
9
+ import { withCommandCleanup } from "./commandCleanup.ts";
10
+
11
+ type AppCleanup = () => void | Promise<void>;
9
12
  type AppBootstrap = () => Promise<{ routes: Record<string, unknown> }>;
10
13
 
11
14
  function isSpaDocumentedPath(path: string, spaPrefix: string): boolean {
@@ -62,71 +65,94 @@ async function registerAppOpenApiRoutes(bootstrap: AppBootstrap): Promise<void>
62
65
  registerOpenApiRouteMap(unregisteredKeepers, ["global", "api"]);
63
66
  }
64
67
 
65
- function createOpenApiGenerateCommand(bootstrap: AppBootstrap) {
66
- return async function openapiGenerateCommand(): Promise<void> {
67
- await registerAppOpenApiRoutes(bootstrap);
68
-
69
- const spec = generateOpenApiSpec(routeRegistry.list());
70
- const jsonPath = join(process.cwd(), "docs/openapi.json");
71
- await mkdir(dirname(jsonPath), { recursive: true });
72
- await writeFile(jsonPath, renderOpenApiDocument(spec), "utf8");
73
-
74
- console.log(`OpenAPI spec written to ${jsonPath} (${routeRegistry.list().length} routes).`);
68
+ function createOpenApiCommand(
69
+ bootstrap: AppBootstrap,
70
+ operation: () => Promise<number | undefined>,
71
+ close?: AppCleanup,
72
+ ) {
73
+ return async (): Promise<void> => {
74
+ const exitCode = await withCommandCleanup(async () => {
75
+ await registerAppOpenApiRoutes(bootstrap);
76
+ return operation();
77
+ }, close);
78
+ if (exitCode === 1) process.exit(1);
75
79
  };
76
80
  }
77
81
 
78
- function createOpenApiValidateCommand(bootstrap: AppBootstrap) {
79
- return async function openapiValidateCommand(): Promise<void> {
80
- await registerAppOpenApiRoutes(bootstrap);
81
-
82
- const spec = generateOpenApiSpec(routeRegistry.list());
83
- const errors = validateOpenApiSpec(spec);
82
+ function createOpenApiGenerateCommand(bootstrap: AppBootstrap, close?: AppCleanup) {
83
+ return createOpenApiCommand(
84
+ bootstrap,
85
+ async () => {
86
+ const spec = generateOpenApiSpec(routeRegistry.list());
87
+ const jsonPath = join(process.cwd(), "docs/openapi.json");
88
+ await mkdir(dirname(jsonPath), { recursive: true });
89
+ await writeFile(jsonPath, renderOpenApiDocument(spec), "utf8");
90
+
91
+ console.log(`OpenAPI spec written to ${jsonPath} (${routeRegistry.list().length} routes).`);
92
+ return undefined;
93
+ },
94
+ close,
95
+ );
96
+ }
84
97
 
85
- if (errors.length > 0) {
86
- console.error("OpenAPI validation failed:");
87
- for (const error of errors) {
88
- console.error(`- ${error}`);
98
+ function createOpenApiValidateCommand(bootstrap: AppBootstrap, close?: AppCleanup) {
99
+ return createOpenApiCommand(
100
+ bootstrap,
101
+ async () => {
102
+ const spec = generateOpenApiSpec(routeRegistry.list());
103
+ const errors = validateOpenApiSpec(spec);
104
+
105
+ if (errors.length > 0) {
106
+ console.error("OpenAPI validation failed:");
107
+ for (const error of errors) {
108
+ console.error(`- ${error}`);
109
+ }
110
+ return 1;
89
111
  }
90
- process.exit(1);
91
- }
92
112
 
93
- console.log(`OpenAPI spec valid (${routeRegistry.list().length} routes).`);
94
- };
113
+ console.log(`OpenAPI spec valid (${routeRegistry.list().length} routes).`);
114
+ return undefined;
115
+ },
116
+ close,
117
+ );
95
118
  }
96
119
 
97
- function createOpenApiCheckCommand(bootstrap: AppBootstrap) {
98
- return async function openapiCheckCommand(): Promise<void> {
99
- await registerAppOpenApiRoutes(bootstrap);
100
-
101
- const spec = generateOpenApiSpec(routeRegistry.list());
102
- const errors = validateOpenApiSpec(spec);
103
-
104
- if (errors.length > 0) {
105
- console.error("OpenAPI validation failed:");
106
- for (const error of errors) {
107
- console.error(`- ${error}`);
120
+ function createOpenApiCheckCommand(bootstrap: AppBootstrap, close?: AppCleanup) {
121
+ return createOpenApiCommand(
122
+ bootstrap,
123
+ async () => {
124
+ const spec = generateOpenApiSpec(routeRegistry.list());
125
+ const errors = validateOpenApiSpec(spec);
126
+
127
+ if (errors.length > 0) {
128
+ console.error("OpenAPI validation failed:");
129
+ for (const error of errors) {
130
+ console.error(`- ${error}`);
131
+ }
132
+ return 1;
108
133
  }
109
- process.exit(1);
110
- }
111
134
 
112
- const generated = renderOpenApiDocument(spec);
113
- const jsonPath = join(process.cwd(), "docs/openapi.json");
114
- const committed = await readFile(jsonPath, "utf8");
115
-
116
- if (committed !== generated) {
117
- console.error("OpenAPI spec drift detected.");
118
- console.error("Run `strata openapi:generate` and commit docs/openapi.json.");
119
- console.error(
120
- "Add that generate step next to `strata openapi:check` in CI so new routes cannot ship undocumented.",
121
- );
122
- process.exit(1);
123
- }
135
+ const generated = renderOpenApiDocument(spec);
136
+ const jsonPath = join(process.cwd(), "docs/openapi.json");
137
+ const committed = await readFile(jsonPath, "utf8");
138
+
139
+ if (committed !== generated) {
140
+ console.error("OpenAPI spec drift detected.");
141
+ console.error("Run `strata openapi:generate` and commit docs/openapi.json.");
142
+ console.error(
143
+ "Add that generate step next to `strata openapi:check` in CI so new routes cannot ship undocumented.",
144
+ );
145
+ return 1;
146
+ }
124
147
 
125
- console.log(`OpenAPI spec matches committed file (${routeRegistry.list().length} routes).`);
126
- };
148
+ console.log(`OpenAPI spec matches committed file (${routeRegistry.list().length} routes).`);
149
+ return undefined;
150
+ },
151
+ close,
152
+ );
127
153
  }
128
154
 
129
- export type { AppBootstrap };
155
+ export type { AppBootstrap, AppCleanup };
130
156
  export {
131
157
  createOpenApiCheckCommand,
132
158
  createOpenApiGenerateCommand,
@@ -5,6 +5,7 @@ import {
5
5
  } from "@getstrata/core/queue/createAppQueue";
6
6
  import { jobRegistry } from "@getstrata/core/queue/jobRegistry";
7
7
  import { resolveApplicationQueue } from "@getstrata/core/runtime/applicationRegistry";
8
+ import { withCommandCleanup } from "./commandCleanup.ts";
8
9
  import type { StrataCommand } from "./types.ts";
9
10
 
10
11
  type QueueFailedBoot = () => unknown | Promise<unknown>;
@@ -61,15 +62,20 @@ async function queueFlushFailedCommand(boot?: QueueFailedBoot): Promise<void> {
61
62
  console.log(`Removed ${deleted} failed job(s).`);
62
63
  }
63
64
 
64
- function createQueueFailedCommands(boot?: QueueFailedBoot): {
65
+ function createQueueFailedCommands(
66
+ boot?: QueueFailedBoot,
67
+ close?: () => void | Promise<void>,
68
+ ): {
65
69
  queueFailedCommand: StrataCommand;
66
70
  queueRetryCommand: StrataCommand;
67
71
  queueFlushFailedCommand: StrataCommand;
68
72
  } {
69
73
  return {
70
- queueFailedCommand: async () => queueFailedCommand(boot),
71
- queueRetryCommand: async (id?: string) => queueRetryCommand(id, boot),
72
- queueFlushFailedCommand: async () => queueFlushFailedCommand(boot),
74
+ queueFailedCommand: async () => withCommandCleanup(() => queueFailedCommand(boot), close),
75
+ queueRetryCommand: async (id?: string) =>
76
+ withCommandCleanup(() => queueRetryCommand(id, boot), close),
77
+ queueFlushFailedCommand: async () =>
78
+ withCommandCleanup(() => queueFlushFailedCommand(boot), close),
73
79
  };
74
80
  }
75
81