@geekmidas/cloud 10.0.0-alpha.33 → 10.0.0-alpha.35

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geekmidas/cloud",
3
- "version": "10.0.0-alpha.33",
3
+ "version": "10.0.0-alpha.35",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist",
@@ -41,8 +41,8 @@
41
41
  },
42
42
  "dependencies": {
43
43
  "lodash.snakecase": "~4.1.1",
44
- "@geekmidas/envkit": "10.0.0-alpha.33",
45
- "@geekmidas/manifest": "10.0.0-alpha.33"
44
+ "@geekmidas/envkit": "10.0.0-alpha.35",
45
+ "@geekmidas/manifest": "10.0.0-alpha.35"
46
46
  },
47
47
  "devDependencies": {
48
48
  "@types/lodash.get": "~4.4.9",
@@ -58,10 +58,10 @@
58
58
  "@pulumi/aws": ">=7.47.0",
59
59
  "@pulumi/random": ">=4.21.2",
60
60
  "@pulumi/pulumi": ">=3.263.0",
61
- "@geekmidas/manifest": "^10.0.0-alpha.33",
62
- "@geekmidas/storage": "^10.0.0-alpha.33",
63
- "@geekmidas/events": "^10.0.0-alpha.33",
64
- "@geekmidas/db": "^10.0.0-alpha.33"
61
+ "@geekmidas/manifest": "^10.0.0-alpha.35",
62
+ "@geekmidas/storage": "^10.0.0-alpha.35",
63
+ "@geekmidas/events": "^10.0.0-alpha.35",
64
+ "@geekmidas/db": "^10.0.0-alpha.35"
65
65
  },
66
66
  "peerDependenciesMeta": {
67
67
  "@geekmidas/manifest": {
@@ -3,6 +3,7 @@ import {
3
3
  PUBLIC,
4
4
  PUBLIC_PREFIX,
5
5
  providedKeyFor,
6
+ schemeBase,
6
7
  } from '@geekmidas/manifest';
7
8
  import { afterEach, describe, expect, it, vi } from 'vitest';
8
9
  import { DatabaseNeedsVpc } from '../aws/Database';
@@ -217,6 +218,37 @@ describe('fromManifest', () => {
217
218
  );
218
219
  });
219
220
 
221
+ it('builds nothing for a mobile app, and has the surfaces it calls trust its scheme', async () => {
222
+ vi.spyOn(console, 'log').mockImplementation(() => {});
223
+
224
+ const provisioned = fromManifest(
225
+ stack,
226
+ {
227
+ ...manifest(),
228
+ App: {
229
+ kind: 'mobile-app',
230
+ id: 'App',
231
+ variant: 'expo',
232
+ app: { path: 'apps/app' },
233
+ dependencies: [{ target: 'Auth', kind: 'rest-api' }],
234
+ provides: ['APP_SCHEME'],
235
+ },
236
+ } as ConstructManifest,
237
+ overrides,
238
+ { cache: 'db' },
239
+ );
240
+
241
+ // EAS and the stores ship it — this stack has nothing to create for it.
242
+ expect(provisioned.App).toBeUndefined();
243
+ const origins = String(
244
+ await settle(provisioned.Auth!.provides().trustedOrigins),
245
+ ).split(',');
246
+ // The bare scheme deployed: the one the store build registers.
247
+ const scheme = `${schemeBase($app.name)}://`;
248
+ expect(origins).toContain(scheme);
249
+ expect(origins).toContain(`${scheme}*`);
250
+ });
251
+
220
252
  it('prints every route and exactly what it can reach', () => {
221
253
  const log = vi.spyOn(console, 'log').mockImplementation(() => {});
222
254
 
@@ -65,6 +65,8 @@ export class DatabaseBootstrap {
65
65
  runtime: string;
66
66
  owner: string;
67
67
  reader?: string;
68
+ /** The database's own roles, whose owner may create in it. */
69
+ ownsDatabase?: boolean;
68
70
  }): { runtime: $util.Output<string>; reader?: $util.Output<string> } {
69
71
  const runtime = this.password(tenant.runtime);
70
72
  const reader = tenant.reader ? this.password(tenant.reader) : undefined;
@@ -75,6 +77,7 @@ export class DatabaseBootstrap {
75
77
  runtime: tenant.runtime,
76
78
  owner: tenant.owner,
77
79
  ...(tenant.reader ? { reader: tenant.reader } : {}),
80
+ ...(tenant.ownsDatabase ? { ownsDatabase: true } : {}),
78
81
  // Filled in by `bootstrapEvent` once the outputs resolve. The function
79
82
  // reads them straight out of its input and never fetches a secret, so
80
83
  // it needs no IAM to read one.
@@ -28,6 +28,11 @@ export interface BootstrapTenant {
28
28
  runtime: string;
29
29
  owner: string;
30
30
  reader?: string;
31
+ /**
32
+ * The database's own roles rather than a tenant's: its owner may create in
33
+ * the database, for trusted extensions.
34
+ */
35
+ ownsDatabase?: boolean;
31
36
  passwords: { runtime: string; owner: string; reader?: string };
32
37
  }
33
38
 
@@ -75,7 +80,11 @@ export async function handler(event: BootstrapEvent): Promise<BootstrapResult> {
75
80
 
76
81
  try {
77
82
  for (const tenant of event.tenants) {
78
- for (const statement of roleStatements(tenant)) {
83
+ const spec = {
84
+ ...tenant,
85
+ ...(tenant.ownsDatabase ? { database: event.master.database } : {}),
86
+ };
87
+ for (const statement of roleStatements(spec)) {
79
88
  if (statement.exists) {
80
89
  const { rows } = await client.query(
81
90
  statement.exists.sql,
@@ -26,10 +26,12 @@ import {
26
26
  cookieDomain,
27
27
  DEFAULT_POSTGRES_VERSION,
28
28
  dependentsOf,
29
+ mobileOrigins,
29
30
  PUBLIC,
30
31
  PUBLIC_PREFIX,
31
32
  providedKeyFor,
32
33
  provisionOrder,
34
+ schemeBase,
33
35
  } from '@geekmidas/manifest';
34
36
  import {
35
37
  Cache,
@@ -241,6 +243,7 @@ const PROVISIONERS: Partial<Record<DeclarationKind, Provisioner>> = {
241
243
  schema: d.schema,
242
244
  runtime,
243
245
  owner: ownerRole(runtime),
246
+ ownsDatabase: true,
244
247
  ...(hasReader(d.id, context) ? { reader: readerRole(runtime) } : {}),
245
248
  });
246
249
 
@@ -647,6 +650,8 @@ async function callersOf(
647
650
  id: string,
648
651
  manifest: ConstructManifest,
649
652
  provisioned: ProvisionedManifest,
653
+ /** The app's name — a mobile caller's scheme base when it gives none. */
654
+ project: string,
650
655
  ): Promise<{ trustedOrigins: string; cookieDomain?: string }> {
651
656
  // Every address here is a Pulumi output, not a string — a CloudFront domain
652
657
  // and an API Gateway endpoint are both known only after their resource
@@ -671,8 +676,17 @@ async function callersOf(
671
676
  const own = await resolved(provisioned[id]?.provides().url);
672
677
  const domain = cookieDomain([...(own ? [own] : []), ...origins]);
673
678
 
679
+ // A mobile caller is reached by its scheme — the bare one, deployed: what
680
+ // the store build registers.
681
+ const schemes = dependentsOf(manifest, id).flatMap((caller) => {
682
+ const app = manifest[caller];
683
+ return app?.kind === 'mobile-app'
684
+ ? mobileOrigins(schemeBase(project, app.scheme))
685
+ : [];
686
+ });
687
+
674
688
  return {
675
- trustedOrigins: origins.join(','),
689
+ trustedOrigins: [...origins, ...schemes].join(','),
676
690
  ...(domain ? { cookieDomain: domain } : {}),
677
691
  };
678
692
  }
@@ -810,6 +824,10 @@ export function fromManifest(
810
824
  for (const id of [...rest, ...sites]) {
811
825
  const declaration = manifest[id];
812
826
  if (!declaration) continue;
827
+ // A mobile app ships through EAS and the stores, not this stack. It is
828
+ // in the manifest for what it makes the surfaces trust, which
829
+ // `callersOf` reads off the graph.
830
+ if (declaration.kind === 'mobile-app') continue;
813
831
 
814
832
  const component = provisionerFor(declaration.kind)(
815
833
  stack,
@@ -840,7 +858,7 @@ export function fromManifest(
840
858
  for (const [id, deferred] of callers) {
841
859
  // Resolved with the promise itself: the addresses it needs are Pulumi
842
860
  // outputs, so the answer arrives when they do.
843
- deferred.resolve(callersOf(id, manifest, provisioned));
861
+ deferred.resolve(callersOf(id, manifest, provisioned, $app.name));
844
862
  }
845
863
 
846
864
  // Last, and only now: the roles exist as passwords and secrets from the