@genoacms/adapter-gcp 0.8.2-1 → 0.9.0

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.
Files changed (52) hide show
  1. package/dist/authentication/identity-platform/descriptor.d.ts +18 -0
  2. package/dist/authentication/identity-platform/descriptor.js +14 -0
  3. package/dist/authentication/identity-platform/runtime.d.ts +4 -0
  4. package/dist/authentication/identity-platform/runtime.js +140 -0
  5. package/dist/database/descriptor.d.ts +16 -0
  6. package/dist/database/descriptor.js +7 -0
  7. package/dist/database/runtime.d.ts +5 -0
  8. package/dist/database/runtime.js +68 -0
  9. package/dist/deployment/archive.d.ts +4 -0
  10. package/dist/deployment/archive.js +46 -0
  11. package/dist/deployment/descriptor.d.ts +24 -0
  12. package/dist/deployment/descriptor.js +20 -0
  13. package/dist/deployment/functions.d.ts +16 -0
  14. package/dist/deployment/functions.js +66 -0
  15. package/dist/deployment/procedure.d.ts +4 -0
  16. package/dist/deployment/procedure.js +16 -0
  17. package/dist/deployment/settings.d.ts +22 -0
  18. package/dist/deployment/settings.js +58 -0
  19. package/dist/secrets/descriptor.d.ts +14 -0
  20. package/dist/secrets/descriptor.js +7 -0
  21. package/dist/secrets/runtime.d.ts +14 -0
  22. package/dist/secrets/runtime.js +147 -0
  23. package/dist/shared/serviceAccount.d.ts +16 -0
  24. package/dist/shared/serviceAccount.js +12 -0
  25. package/dist/storage/descriptor.d.ts +14 -0
  26. package/dist/storage/descriptor.js +7 -0
  27. package/dist/storage/runtime.d.ts +8 -0
  28. package/dist/storage/runtime.js +178 -0
  29. package/package.json +46 -27
  30. package/deployment/snippets/index.js +0 -8
  31. package/dist/config.d.ts +0 -2
  32. package/dist/config.js +0 -2
  33. package/dist/services/authorization/index.d.ts +0 -3
  34. package/dist/services/authorization/index.js +0 -24
  35. package/dist/services/database/index.d.ts +0 -7
  36. package/dist/services/database/index.js +0 -63
  37. package/dist/services/deployment/deploy.d.ts +0 -2
  38. package/dist/services/deployment/deploy.js +0 -115
  39. package/dist/services/deployment/index.d.ts +0 -4
  40. package/dist/services/deployment/index.js +0 -6
  41. package/dist/services/storage/index.d.ts +0 -12
  42. package/dist/services/storage/index.js +0 -87
  43. package/dist/services/storage/storage.d.ts +0 -3
  44. package/dist/services/storage/storage.js +0 -20
  45. package/src/config.ts +0 -4
  46. package/src/genoa.config.d.ts +0 -51
  47. package/src/services/authorization/index.ts +0 -28
  48. package/src/services/database/index.ts +0 -88
  49. package/src/services/deployment/deploy.ts +0 -125
  50. package/src/services/deployment/index.ts +0 -13
  51. package/src/services/storage/index.ts +0 -119
  52. package/src/services/storage/storage.ts +0 -27
@@ -0,0 +1,147 @@
1
+ import { defineRuntime } from '@genoacms/contracts';
2
+ import { assertValidSecretKey } from '@genoacms/contracts/secrets';
3
+ import { SecretManagerServiceClient } from '@google-cloud/secret-manager';
4
+ /**
5
+ * The GenoaCMS `secrets` service backed by GCP Secret Manager.
6
+ *
7
+ * Secret Manager is versioned; this contract is not. Writes add a version and reads always take
8
+ * `latest`, so version history exists but is never consulted. That is deliberate — building the
9
+ * contract on a provider-specific behavior would leave the abstraction unimplementable elsewhere.
10
+ * `setSecret` therefore destroys the versions it supersedes (architecture GD4). Secrets created here
11
+ * keep a destroyed version disabled for seven days before it is gone, so a bad overwrite can be undone
12
+ * by hand.
13
+ */
14
+ export default defineRuntime({
15
+ create({ projectId, credentials }) {
16
+ const client = new SecretManagerServiceClient({
17
+ projectId,
18
+ ...(credentials === undefined ? {} : { credentials })
19
+ });
20
+ const GRPC_NOT_FOUND = 5;
21
+ const GRPC_ALREADY_EXISTS = 6;
22
+ const parent = `projects/${projectId}`;
23
+ const secretName = (key) => `${parent}/secrets/${key}`;
24
+ const latestVersionName = (key) => `${secretName(key)}/versions/latest`;
25
+ // SEC-7, GD4
26
+ const VERSION_DESTROY_TTL = { seconds: 7 * 24 * 60 * 60 };
27
+ // SEC-7
28
+ const newSecret = () => ({ replication: { automatic: {} }, versionDestroyTtl: VERSION_DESTROY_TTL });
29
+ // SEC-8, SEC-9
30
+ function versionNumber(name) {
31
+ const number = Number(name.split('/').at(-1));
32
+ if (!Number.isInteger(number) || number < 1)
33
+ throw new Error(`secrets/unexpected-version-name: ${name}`);
34
+ return number;
35
+ }
36
+ // SEC-8
37
+ async function destroySuperseded(key, added) {
38
+ const addedNumber = versionNumber(added);
39
+ const [versions] = await client.listSecretVersions({ parent: secretName(key), filter: 'state:ENABLED' });
40
+ for (const version of versions) {
41
+ if (typeof version.name === 'string' && versionNumber(version.name) < addedNumber) {
42
+ await client.destroySecretVersion({ name: version.name });
43
+ }
44
+ }
45
+ }
46
+ // SEC-9, GD4
47
+ async function cleanUp(key, added) {
48
+ try {
49
+ await destroySuperseded(key, added);
50
+ }
51
+ catch (error) {
52
+ console.warn(`secrets/cleanup-failed: ${key}: ${error.message}`);
53
+ }
54
+ }
55
+ function hasStatusCode(error, code) {
56
+ return typeof error === 'object' && error !== null && error.code === code;
57
+ }
58
+ // SEC-5
59
+ async function ensureSecretExists(key) {
60
+ try {
61
+ await client.getSecret({ name: secretName(key) });
62
+ }
63
+ catch (error) {
64
+ if (!hasStatusCode(error, GRPC_NOT_FOUND))
65
+ throw error;
66
+ try {
67
+ await client.createSecret({
68
+ parent,
69
+ secretId: key,
70
+ secret: newSecret()
71
+ });
72
+ }
73
+ catch (createError) {
74
+ if (!hasStatusCode(createError, GRPC_ALREADY_EXISTS))
75
+ throw createError;
76
+ }
77
+ }
78
+ }
79
+ // SEC-3, SEC-4
80
+ const getSecret = async (key) => {
81
+ assertValidSecretKey(key);
82
+ try {
83
+ const [version] = await client.accessSecretVersion({ name: latestVersionName(key) });
84
+ const data = version.payload?.data;
85
+ if (data === null || data === undefined)
86
+ return undefined;
87
+ return typeof data === 'string' ? data : Buffer.from(data).toString('utf-8');
88
+ }
89
+ catch (error) {
90
+ if (hasStatusCode(error, GRPC_NOT_FOUND))
91
+ return undefined;
92
+ throw error;
93
+ }
94
+ };
95
+ const setSecret = async (key, value) => {
96
+ assertValidSecretKey(key);
97
+ await ensureSecretExists(key);
98
+ const [version] = await client.addSecretVersion({
99
+ parent: secretName(key),
100
+ payload: { data: Buffer.from(value, 'utf-8') }
101
+ });
102
+ if (typeof version.name === 'string')
103
+ await cleanUp(key, version.name);
104
+ return true;
105
+ };
106
+ // SEC-6, SEC-10
107
+ const setSecretIfAbsent = async (key, value) => {
108
+ assertValidSecretKey(key);
109
+ try {
110
+ await client.createSecret({
111
+ parent,
112
+ secretId: key,
113
+ secret: newSecret()
114
+ });
115
+ }
116
+ catch (error) {
117
+ if (hasStatusCode(error, GRPC_ALREADY_EXISTS))
118
+ return false;
119
+ throw error;
120
+ }
121
+ await client.addSecretVersion({
122
+ parent: secretName(key),
123
+ payload: { data: Buffer.from(value, 'utf-8') }
124
+ });
125
+ return true;
126
+ };
127
+ // SEC-11
128
+ const deleteSecret = async (key) => {
129
+ assertValidSecretKey(key);
130
+ try {
131
+ await client.deleteSecret({ name: secretName(key) });
132
+ return true;
133
+ }
134
+ catch (error) {
135
+ if (hasStatusCode(error, GRPC_NOT_FOUND))
136
+ return false;
137
+ throw error;
138
+ }
139
+ };
140
+ return {
141
+ getSecret,
142
+ setSecret,
143
+ deleteSecret,
144
+ setSecretIfAbsent
145
+ };
146
+ }
147
+ });
@@ -0,0 +1,16 @@
1
+ /** The fields of a Google service-account key file that the client libraries read. */
2
+ export interface ServiceAccount {
3
+ type: string;
4
+ project_id: string;
5
+ private_key_id: string;
6
+ private_key: string;
7
+ client_email: string;
8
+ client_id: string;
9
+ auth_uri?: string;
10
+ token_uri?: string;
11
+ auth_provider_x509_cert_url?: string;
12
+ client_x509_cert_url?: string;
13
+ universe_domain?: string;
14
+ }
15
+ export declare function unknownOptions(options: unknown, allowed: readonly string[]): string[];
16
+ export declare function requireString(options: unknown, key: string): string[];
@@ -0,0 +1,12 @@
1
+ const asRecord = (options) => typeof options === 'object' && options !== null ? options : {};
2
+ // COM-2
3
+ export function unknownOptions(options, allowed) {
4
+ return Object.keys(asRecord(options))
5
+ .filter(key => !allowed.includes(key))
6
+ .map(key => `unknown option '${key}'`);
7
+ }
8
+ // COM-3
9
+ export function requireString(options, key) {
10
+ const value = asRecord(options)[key];
11
+ return typeof value === 'string' && value !== '' ? [] : [`${key} is required and must be a non-empty string`];
12
+ }
@@ -0,0 +1,14 @@
1
+ import { type Secret } from '@genoacms/contracts';
2
+ import { type ServiceAccount } from '../shared/serviceAccount.js';
3
+ export interface GcpStorageOptions {
4
+ projectId: string;
5
+ /** Omitted: Application Default Credentials, i.e. the runtime's own identity. */
6
+ credentials?: Secret<ServiceAccount>;
7
+ }
8
+ declare module '@genoacms/contracts' {
9
+ interface StorageAdapters {
10
+ '@genoacms/adapter-gcp/storage': GcpStorageOptions;
11
+ }
12
+ }
13
+ declare const _default: import("@genoacms/contracts").AdapterDescriptor<"storage", GcpStorageOptions>;
14
+ export default _default;
@@ -0,0 +1,7 @@
1
+ import { defineStorageAdapter } from '@genoacms/contracts';
2
+ import { unknownOptions, requireString } from '../shared/serviceAccount.js';
3
+ export default defineStorageAdapter({
4
+ runtime: '@genoacms/adapter-gcp/storage/runtime',
5
+ secretOptions: { credentials: 'json' },
6
+ validate: options => [...unknownOptions(options, ['projectId', 'credentials']), ...requireString(options, 'projectId')]
7
+ });
@@ -0,0 +1,8 @@
1
+ import type { Adapter } from '@genoacms/contracts/storage';
2
+ import type { GcpStorageOptions } from './descriptor.js';
3
+ /**
4
+ * One GCS provider. Each construction owns its client and its credential, so two providers on this
5
+ * adapter — two GCP projects — never share either.
6
+ */
7
+ declare const _default: import("@genoacms/contracts").AdapterRuntime<GcpStorageOptions, Adapter>;
8
+ export default _default;
@@ -0,0 +1,178 @@
1
+ import { defineRuntime } from '@genoacms/contracts';
2
+ import { PreconditionFailedError } from '@genoacms/contracts/storage';
3
+ import { Storage } from '@google-cloud/storage';
4
+ const HTTP_PRECONDITION_FAILED = 412;
5
+ const DELETE_CONCURRENCY = 10;
6
+ // STO-9, GF21
7
+ function requestedResults(limit, startAfter) {
8
+ return limit !== undefined && startAfter !== undefined ? limit + 1 : limit;
9
+ }
10
+ // STO-9
11
+ const byUtf8Bytes = (a, b) => Buffer.compare(Buffer.from(a), Buffer.from(b));
12
+ // STO-9
13
+ function firstByName(names, limit) {
14
+ const sorted = [...names].sort(byUtf8Bytes);
15
+ return new Set(limit === undefined ? sorted : sorted.slice(0, limit));
16
+ }
17
+ // STO-11, GF29
18
+ async function deleteAtMost(concurrency, files) {
19
+ let next = 0;
20
+ let failure;
21
+ const work = async () => {
22
+ while (failure === undefined && next < files.length) {
23
+ const file = files[next++];
24
+ try {
25
+ await file.delete();
26
+ }
27
+ catch (error) {
28
+ failure ??= { error };
29
+ }
30
+ }
31
+ };
32
+ await Promise.all(Array.from({ length: Math.min(concurrency, files.length) }, work));
33
+ if (failure !== undefined)
34
+ throw failure.error;
35
+ }
36
+ /**
37
+ * One GCS provider. Each construction owns its client and its credential, so two providers on this
38
+ * adapter — two GCP projects — never share either.
39
+ */
40
+ export default defineRuntime({
41
+ create({ projectId, credentials }, ctx) {
42
+ const storage = new Storage(credentials === undefined ? { projectId } : { projectId, credentials });
43
+ const registered = new Set(ctx.resources);
44
+ const getBucket = (name) => {
45
+ if (!registered.has(name))
46
+ throw new Error('bucket-unregistered');
47
+ return storage.bucket(name);
48
+ };
49
+ // STO-4
50
+ const getObject = async ({ bucket, name }) => {
51
+ const bucketInstance = getBucket(bucket);
52
+ const file = bucketInstance.file(name);
53
+ let version;
54
+ try {
55
+ const [metadata] = await file.getMetadata();
56
+ version = metadata.generation === undefined ? undefined : String(metadata.generation);
57
+ }
58
+ catch {
59
+ version = undefined;
60
+ }
61
+ return {
62
+ data: file.createReadStream(),
63
+ version
64
+ };
65
+ };
66
+ const getPublicURL = async ({ bucket, name }) => {
67
+ const bucketInstance = getBucket(bucket);
68
+ const file = bucketInstance.file(name);
69
+ return file.publicUrl();
70
+ };
71
+ const getSignedURL = async ({ bucket, name }, expires) => {
72
+ const bucketInstance = getBucket(bucket);
73
+ const file = bucketInstance.file(name);
74
+ const [url] = await file.getSignedUrl({
75
+ action: 'read',
76
+ expires
77
+ });
78
+ return url;
79
+ };
80
+ const uploadObject = async ({ bucket, name }, stream, options) => {
81
+ const bucketInstance = getBucket(bucket);
82
+ const file = bucketInstance.file(name);
83
+ const { ifVersion, ifAbsent, ...saveOptions } = options ?? {};
84
+ // STO-6
85
+ const generation = ifAbsent === true ? 0 : ifVersion === undefined ? undefined : Number(ifVersion);
86
+ try {
87
+ await file.save(stream, generation === undefined
88
+ ? saveOptions
89
+ : { ...saveOptions, preconditionOpts: { ifGenerationMatch: generation } });
90
+ }
91
+ catch (error) {
92
+ if (error.code === HTTP_PRECONDITION_FAILED) {
93
+ throw new PreconditionFailedError({ bucket, name }, ifAbsent === true
94
+ ? 'object already exists'
95
+ : 'object changed since it was read');
96
+ }
97
+ throw error;
98
+ }
99
+ };
100
+ const moveObject = async ({ bucket, name }, newName) => {
101
+ const bucketInstance = getBucket(bucket);
102
+ const file = bucketInstance.file(name);
103
+ await file.move(newName);
104
+ };
105
+ const deleteObject = async ({ bucket, name }) => {
106
+ const bucketInstance = getBucket(bucket);
107
+ const file = bucketInstance.file(name);
108
+ await file.delete();
109
+ };
110
+ const listDirectory = async ({ bucket, name }, listingParams = {}) => {
111
+ const bucketInstance = getBucket(bucket);
112
+ const options = {
113
+ autoPaginate: false,
114
+ prefix: name,
115
+ maxResults: requestedResults(listingParams?.limit, listingParams?.startAfter),
116
+ startOffset: listingParams?.startAfter,
117
+ delimiter: '/'
118
+ };
119
+ let [files, , apiResponse] = (await bucketInstance.getFiles(options));
120
+ files = files.filter((file) => !file.name.endsWith('.folderPlaceholder'));
121
+ // STO-9, GF21
122
+ const skipped = (itemName) => itemName === name || itemName === listingParams?.startAfter;
123
+ files = files.filter(f => !skipped(f.name));
124
+ const prefixes = (apiResponse?.prefixes ?? []).filter((item) => !skipped(item));
125
+ const kept = firstByName([...files.map(f => f.name), ...prefixes], listingParams?.limit);
126
+ return {
127
+ files: files.filter(f => kept.has(f.name)).map((file) => {
128
+ return {
129
+ name: file.name,
130
+ // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
131
+ size: file.metadata.size ? parseInt(file.metadata.size) : 0,
132
+ lastModified: new Date(file.metadata.updated)
133
+ };
134
+ }),
135
+ directories: prefixes.filter((item) => kept.has(item)).map(i => {
136
+ const object = {
137
+ bucket,
138
+ name: i
139
+ };
140
+ return object;
141
+ })
142
+ };
143
+ };
144
+ const createDirectory = async ({ bucket, name }) => {
145
+ const bucketInstance = getBucket(bucket);
146
+ const file = bucketInstance.file(`${name}/.folderPlaceholder`);
147
+ await file.save('');
148
+ };
149
+ // STO-11, GD7, GF29
150
+ const deleteDirectory = async ({ bucket, name }) => {
151
+ const bucketInstance = getBucket(bucket);
152
+ let query = { prefix: name, autoPaginate: false };
153
+ while (query !== undefined) {
154
+ const [files, nextQuery] = await bucketInstance.getFiles(query);
155
+ await deleteAtMost(DELETE_CONCURRENCY, files);
156
+ query = nextQuery ?? undefined;
157
+ }
158
+ };
159
+ // STO-12, GD7, GF22
160
+ const moveDirectory = async ({ bucket, name }, newName) => {
161
+ const [files] = await getBucket(bucket).getFiles({ prefix: name });
162
+ for (const file of files)
163
+ await file.move(newName + file.name.slice(name.length));
164
+ };
165
+ return {
166
+ getObject,
167
+ getPublicURL,
168
+ getSignedURL,
169
+ uploadObject,
170
+ moveObject,
171
+ deleteObject,
172
+ listDirectory,
173
+ createDirectory,
174
+ deleteDirectory,
175
+ moveDirectory
176
+ };
177
+ }
178
+ });
package/package.json CHANGED
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "name": "@genoacms/adapter-gcp",
3
- "version": "0.8.2-1",
3
+ "version": "0.9.0",
4
4
  "type": "module",
5
5
  "description": "Implementation of abstraction layer of GenoaCMS for GCP",
6
6
  "repository": {
7
7
  "type": "git",
8
- "url": "git+https://github.com/GenoaCMS/adapter-gcp.git"
8
+ "url": "git+https://github.com/GenoaCMS/genoacms.git",
9
+ "directory": "packages/adapter-gcp"
9
10
  },
10
11
  "author": {
11
12
  "name": "Filip Holčík",
@@ -13,19 +14,21 @@
13
14
  },
14
15
  "license": "ISC",
15
16
  "bugs": {
16
- "url": "https://github.com/GenoaCMS/adapter-gcp/issues"
17
+ "url": "https://github.com/GenoaCMS/genoacms/issues"
17
18
  },
18
- "homepage": "https://github.com/GenoaCMS/adapter-gcp#readme",
19
+ "homepage": "https://github.com/GenoaCMS/genoacms/tree/main/packages/adapter-gcp#readme",
19
20
  "publishConfig": {
20
21
  "provenance": true
21
22
  },
22
23
  "dependencies": {
23
- "@genoacms/cloudabstraction": "^0.8.2",
24
24
  "@google-cloud/firestore": "^7.1.0",
25
25
  "@google-cloud/functions": "^3.4.0",
26
- "@google-cloud/resource-manager": "^5.1.0",
26
+ "@google-cloud/secret-manager": "^7.0.0",
27
27
  "@google-cloud/storage": "^7.7.0",
28
- "archiver": "^7.0.0"
28
+ "archiver": "^7.0.0",
29
+ "google-auth-library": "^9.15.1",
30
+ "@genoacms/contracts": "^0.0.2",
31
+ "@genoacms/sveltekit-adapter-cloud-run-functions": "^1.0.1"
29
32
  },
30
33
  "devDependencies": {
31
34
  "@types/archiver": "^6.0.2",
@@ -37,38 +40,54 @@
37
40
  "eslint-plugin-promise": "^6.1.1",
38
41
  "rimraf": "^5.0.5",
39
42
  "typescript": "^5.2.2",
40
- "vitest": "^1.0.4"
41
- },
42
- "peerDependencies": {
43
- "@genoacms/sveltekit-adapter-cloud-run-functions": "^1.0.0"
43
+ "vitest": "^3.2.7",
44
+ "@genoacms/conformance": "^0.0.2"
44
45
  },
45
46
  "files": [
46
- "src",
47
- "deployment",
48
47
  "dist"
49
48
  ],
50
49
  "exports": {
51
- "./authorization": {
52
- "import": "./dist/services/authorization/index.js",
53
- "types": "./dist/services/authorization/index.d.ts"
50
+ "./storage": {
51
+ "types": "./dist/storage/descriptor.d.ts",
52
+ "import": "./dist/storage/descriptor.js"
53
+ },
54
+ "./storage/runtime": {
55
+ "types": "./dist/storage/runtime.d.ts",
56
+ "import": "./dist/storage/runtime.js"
54
57
  },
55
58
  "./database": {
56
- "import": "./dist/services/database/index.js",
57
- "types": "./dist/services/database/index.d.ts"
59
+ "types": "./dist/database/descriptor.d.ts",
60
+ "import": "./dist/database/descriptor.js"
58
61
  },
59
- "./deployment": {
60
- "import": "./dist/services/deployment/index.js",
61
- "types": "./dist/services/deployment/index.d.ts"
62
+ "./database/runtime": {
63
+ "types": "./dist/database/runtime.d.ts",
64
+ "import": "./dist/database/runtime.js"
62
65
  },
63
- "./storage": {
64
- "import": "./dist/services/storage/index.js",
65
- "types": "./dist/services/storage/index.d.ts"
66
+ "./secrets": {
67
+ "types": "./dist/secrets/descriptor.d.ts",
68
+ "import": "./dist/secrets/descriptor.js"
69
+ },
70
+ "./secrets/runtime": {
71
+ "types": "./dist/secrets/runtime.d.ts",
72
+ "import": "./dist/secrets/runtime.js"
73
+ },
74
+ "./authentication/identity-platform": {
75
+ "types": "./dist/authentication/identity-platform/descriptor.d.ts",
76
+ "import": "./dist/authentication/identity-platform/descriptor.js"
77
+ },
78
+ "./authentication/identity-platform/runtime": {
79
+ "types": "./dist/authentication/identity-platform/runtime.d.ts",
80
+ "import": "./dist/authentication/identity-platform/runtime.js"
81
+ },
82
+ "./deployment": {
83
+ "types": "./dist/deployment/descriptor.d.ts",
84
+ "import": "./dist/deployment/descriptor.js"
66
85
  }
67
86
  },
68
87
  "scripts": {
69
88
  "build": "rimraf dist && tsc",
70
- "test:all": "npm run build && vitest --silent=false --config node_modules/@genoacms/cloudabstraction/test/vitest.config.js --run node_modules/@genoacms/cloudabstraction/test/all.test.js",
71
- "test:database": "npm run build && vitest --silent=false --config node_modules/@genoacms/cloudabstraction/test/vitest.config.js --run node_modules/@genoacms/cloudabstraction/test/database.test.js",
72
- "test:storage": "npm run build && vitest --silent=false --config node_modules/@genoacms/cloudabstraction/test/vitest.config.js --run node_modules/@genoacms/cloudabstraction/test/storage.test.js"
89
+ "test": "vitest run",
90
+ "test:conformance": "vitest run test/conformance.test.ts",
91
+ "test:contract": "vitest run test/contract test/conformance.test.ts"
73
92
  }
74
93
  }
@@ -1,8 +0,0 @@
1
- import { handler } from './node_modules/@genoacms/core/build/index.js'
2
-
3
- function genoacms (req, res) {
4
- handler(req, res, undefined)
5
- }
6
-
7
- export { genoacms }
8
-
package/dist/config.d.ts DELETED
@@ -1,2 +0,0 @@
1
- declare const _default: any;
2
- export default _default;
package/dist/config.js DELETED
@@ -1,2 +0,0 @@
1
- import { config } from '@genoacms/cloudabstraction';
2
- export default config;
@@ -1,3 +0,0 @@
1
- import type { Adapter } from '@genoacms/cloudabstraction/authorization';
2
- declare const isEmailAdmins: Adapter.isEmailAdmins;
3
- export { isEmailAdmins };
@@ -1,24 +0,0 @@
1
- import { getProvider } from '@genoacms/cloudabstraction';
2
- import { ProjectsClient } from '@google-cloud/resource-manager';
3
- const ADAPTER_PATH = '@genoacms/adapter-gcp/authorization';
4
- const provider = getProvider('authorization', ADAPTER_PATH);
5
- const projectId = provider.projectId;
6
- const resourceManager = new ProjectsClient({
7
- projectId,
8
- credentials: provider.credentials
9
- });
10
- const isEmailAdmins = async (email) => {
11
- const resource = `projects/${projectId}`;
12
- const role = resource + '/roles/genoacms';
13
- const data = await resourceManager.getIamPolicy({ resource });
14
- const policy = data[0];
15
- if ((policy.bindings) == null)
16
- throw new Error('no-bindings');
17
- const adminRole = policy.bindings.find(binding => binding.role === role);
18
- if (adminRole == null)
19
- throw new Error('no-admin-role');
20
- if (adminRole.members == null)
21
- throw new Error('no-principals');
22
- return adminRole.members.includes(`user:${email}`);
23
- };
24
- export { isEmailAdmins };
@@ -1,7 +0,0 @@
1
- import type { Adapter } from '@genoacms/cloudabstraction/database';
2
- declare const createDocument: Adapter['createDocument'];
3
- declare const getCollection: Adapter['getCollection'];
4
- declare const getDocument: Adapter['getDocument'];
5
- declare const updateDocument: Adapter['updateDocument'];
6
- declare const deleteDocument: Adapter['deleteDocument'];
7
- export { createDocument, getDocument, getCollection, updateDocument, deleteDocument };
@@ -1,63 +0,0 @@
1
- import { Firestore } from '@google-cloud/firestore';
2
- import { getProvider } from '@genoacms/cloudabstraction';
3
- const ADAPTER_PATH = '@genoacms/adapter-gcp/database';
4
- const provider = getProvider('database', ADAPTER_PATH);
5
- const firestore = new Firestore({
6
- credentials: provider.credentials,
7
- databaseId: provider.databaseId,
8
- projectId: provider.projectId
9
- });
10
- const createDocument = async (reference, data) => {
11
- const document = await firestore.collection(reference.name).add(data);
12
- const documentReference = {
13
- collection: reference,
14
- id: document.id
15
- };
16
- return {
17
- reference: documentReference,
18
- data
19
- };
20
- };
21
- const getCollection = async (reference) => {
22
- const collection = await firestore.collection(reference.name).get();
23
- const documents = [];
24
- collection.forEach(document => {
25
- const documentData = {};
26
- Object.keys(reference.schema.properties).forEach(key => {
27
- documentData[key] = document.get(key);
28
- });
29
- documents.push({
30
- reference: {
31
- collection: reference,
32
- id: document.id
33
- },
34
- data: document.data()
35
- });
36
- });
37
- return documents;
38
- };
39
- const getDocument = async ({ collection, id }) => {
40
- const document = await firestore.collection(collection.name).doc(id).get();
41
- if (!document.exists)
42
- return undefined;
43
- const documentReference = {
44
- collection,
45
- id
46
- };
47
- const documentSnapshot = {
48
- reference: documentReference,
49
- data: document.data()
50
- };
51
- return documentSnapshot;
52
- };
53
- const updateDocument = async (reference, document) => {
54
- await firestore.collection(reference.collection.name).doc(reference.id).update(document);
55
- return {
56
- reference,
57
- data: document
58
- };
59
- };
60
- const deleteDocument = async (reference) => {
61
- await firestore.collection(reference.collection.name).doc(reference.id).delete();
62
- };
63
- export { createDocument, getDocument, getCollection, updateDocument, deleteDocument };
@@ -1,2 +0,0 @@
1
- declare function deploy(): Promise<void>;
2
- export default deploy;