@genoacms/adapter-gcp 0.8.2 → 0.10.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 (53) hide show
  1. package/LICENSE +21 -0
  2. package/dist/authentication/identity-platform/descriptor.d.ts +18 -0
  3. package/dist/authentication/identity-platform/descriptor.js +14 -0
  4. package/dist/authentication/identity-platform/runtime.d.ts +4 -0
  5. package/dist/authentication/identity-platform/runtime.js +140 -0
  6. package/dist/database/descriptor.d.ts +16 -0
  7. package/dist/database/descriptor.js +7 -0
  8. package/dist/database/runtime.d.ts +5 -0
  9. package/dist/database/runtime.js +68 -0
  10. package/dist/deployment/archive.d.ts +4 -0
  11. package/dist/deployment/archive.js +46 -0
  12. package/dist/deployment/descriptor.d.ts +24 -0
  13. package/dist/deployment/descriptor.js +20 -0
  14. package/dist/deployment/functions.d.ts +16 -0
  15. package/dist/deployment/functions.js +66 -0
  16. package/dist/deployment/procedure.d.ts +4 -0
  17. package/dist/deployment/procedure.js +16 -0
  18. package/dist/deployment/settings.d.ts +22 -0
  19. package/dist/deployment/settings.js +58 -0
  20. package/dist/secrets/descriptor.d.ts +14 -0
  21. package/dist/secrets/descriptor.js +7 -0
  22. package/dist/secrets/runtime.d.ts +14 -0
  23. package/dist/secrets/runtime.js +147 -0
  24. package/dist/shared/serviceAccount.d.ts +16 -0
  25. package/dist/shared/serviceAccount.js +12 -0
  26. package/dist/storage/descriptor.d.ts +14 -0
  27. package/dist/storage/descriptor.js +7 -0
  28. package/dist/storage/runtime.d.ts +8 -0
  29. package/dist/storage/runtime.js +207 -0
  30. package/package.json +47 -28
  31. package/deployment/snippets/index.js +0 -8
  32. package/dist/config.d.ts +0 -2
  33. package/dist/config.js +0 -2
  34. package/dist/services/authorization/index.d.ts +0 -3
  35. package/dist/services/authorization/index.js +0 -24
  36. package/dist/services/database/index.d.ts +0 -7
  37. package/dist/services/database/index.js +0 -63
  38. package/dist/services/deployment/deploy.d.ts +0 -2
  39. package/dist/services/deployment/deploy.js +0 -116
  40. package/dist/services/deployment/index.d.ts +0 -4
  41. package/dist/services/deployment/index.js +0 -6
  42. package/dist/services/storage/index.d.ts +0 -12
  43. package/dist/services/storage/index.js +0 -87
  44. package/dist/services/storage/storage.d.ts +0 -3
  45. package/dist/services/storage/storage.js +0 -20
  46. package/src/config.ts +0 -4
  47. package/src/genoa.config.d.ts +0 -50
  48. package/src/services/authorization/index.ts +0 -28
  49. package/src/services/database/index.ts +0 -88
  50. package/src/services/deployment/deploy.ts +0 -126
  51. package/src/services/deployment/index.ts +0 -13
  52. package/src/services/storage/index.ts +0 -119
  53. 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,207 @@
1
+ import { defineRuntime } from '@genoacms/contracts';
2
+ import { PreconditionFailedError, ObjectNotFoundError, NotADirectoryError, isDirectoryName } from '@genoacms/contracts/storage';
3
+ import { Storage } from '@google-cloud/storage';
4
+ const HTTP_PRECONDITION_FAILED = 412;
5
+ const HTTP_NOT_FOUND = 404;
6
+ const DELETE_CONCURRENCY = 10;
7
+ const hasStatus = (error, status) => error?.code === status;
8
+ // STO-4, STO-7, STOR-16
9
+ async function rejectingMissing(reference, call) {
10
+ try {
11
+ return await call();
12
+ }
13
+ catch (error) {
14
+ if (hasStatus(error, HTTP_NOT_FOUND))
15
+ throw new ObjectNotFoundError(reference);
16
+ throw error;
17
+ }
18
+ }
19
+ // STO-9
20
+ function listingQuery(name, { limit, startAfter, pageToken }) {
21
+ return {
22
+ prefix: name,
23
+ delimiter: '/',
24
+ autoPaginate: false,
25
+ ...(limit === undefined ? {} : { maxResults: limit }),
26
+ ...(startAfter === undefined ? {} : { startOffset: startAfter }),
27
+ ...(pageToken === undefined ? {} : { pageToken })
28
+ };
29
+ }
30
+ // STO-11, GF38
31
+ function everyDepthPage(prefix, pageToken) {
32
+ return pageToken === undefined ? { prefix, autoPaginate: false } : { prefix, autoPaginate: false, pageToken };
33
+ }
34
+ // STO-11, GF29
35
+ async function deleteAtMost(concurrency, files) {
36
+ let next = 0;
37
+ let failure;
38
+ const work = async () => {
39
+ while (failure === undefined && next < files.length) {
40
+ const file = files[next++];
41
+ try {
42
+ await file.delete();
43
+ }
44
+ catch (error) {
45
+ failure ??= { error };
46
+ }
47
+ }
48
+ };
49
+ await Promise.all(Array.from({ length: Math.min(concurrency, files.length) }, work));
50
+ if (failure !== undefined)
51
+ throw failure.error;
52
+ }
53
+ /**
54
+ * One GCS provider. Each construction owns its client and its credential, so two providers on this
55
+ * adapter — two GCP projects — never share either.
56
+ */
57
+ export default defineRuntime({
58
+ create({ projectId, credentials }, ctx) {
59
+ const storage = new Storage(credentials === undefined ? { projectId } : { projectId, credentials });
60
+ const registered = new Set(ctx.resources);
61
+ const getBucket = (name) => {
62
+ if (!registered.has(name))
63
+ throw new Error('bucket-unregistered');
64
+ return storage.bucket(name);
65
+ };
66
+ // STO-3, STOR-3
67
+ const requireDirectoryName = (reference, { allowRoot }) => {
68
+ if (!isDirectoryName(reference.name) || (!allowRoot && reference.name === ''))
69
+ throw new NotADirectoryError(reference);
70
+ };
71
+ const getDirectoryBucket = (reference, options) => {
72
+ const bucketInstance = getBucket(reference.bucket);
73
+ requireDirectoryName(reference, options);
74
+ return bucketInstance;
75
+ };
76
+ // STO-4
77
+ const getObject = async ({ bucket, name }) => {
78
+ const bucketInstance = getBucket(bucket);
79
+ const file = bucketInstance.file(name);
80
+ let version;
81
+ try {
82
+ const [metadata] = await file.getMetadata();
83
+ version = metadata.generation === undefined ? undefined : String(metadata.generation);
84
+ }
85
+ catch (error) {
86
+ if (hasStatus(error, HTTP_NOT_FOUND))
87
+ throw new ObjectNotFoundError({ bucket, name });
88
+ version = undefined;
89
+ }
90
+ return {
91
+ data: file.createReadStream(),
92
+ version
93
+ };
94
+ };
95
+ const getPublicURL = async ({ bucket, name }) => {
96
+ const bucketInstance = getBucket(bucket);
97
+ const file = bucketInstance.file(name);
98
+ return file.publicUrl();
99
+ };
100
+ const getSignedURL = async ({ bucket, name }, expires) => {
101
+ const bucketInstance = getBucket(bucket);
102
+ const file = bucketInstance.file(name);
103
+ const [url] = await file.getSignedUrl({
104
+ action: 'read',
105
+ expires
106
+ });
107
+ return url;
108
+ };
109
+ const uploadObject = async ({ bucket, name }, stream, options) => {
110
+ const bucketInstance = getBucket(bucket);
111
+ const file = bucketInstance.file(name);
112
+ const { ifVersion, ifAbsent } = options ?? {};
113
+ // STO-6
114
+ const generation = ifAbsent === true ? 0 : ifVersion === undefined ? undefined : Number(ifVersion);
115
+ try {
116
+ await file.save(stream, generation === undefined
117
+ ? {}
118
+ : { preconditionOpts: { ifGenerationMatch: generation } });
119
+ }
120
+ catch (error) {
121
+ if (hasStatus(error, HTTP_PRECONDITION_FAILED)) {
122
+ throw new PreconditionFailedError({ bucket, name }, ifAbsent === true
123
+ ? 'object already exists'
124
+ : 'object changed since it was read');
125
+ }
126
+ throw error;
127
+ }
128
+ };
129
+ const moveObject = async ({ bucket, name }, newName) => {
130
+ const bucketInstance = getBucket(bucket);
131
+ const file = bucketInstance.file(name);
132
+ await file.move(newName);
133
+ };
134
+ const deleteObject = async ({ bucket, name }) => {
135
+ const bucketInstance = getBucket(bucket);
136
+ const file = bucketInstance.file(name);
137
+ await rejectingMissing({ bucket, name }, async () => await file.delete());
138
+ };
139
+ // STO-9, GF21
140
+ const listDirectory = async (reference, listingParams = {}) => {
141
+ const bucketInstance = getDirectoryBucket(reference, { allowRoot: true });
142
+ const { bucket, name } = reference;
143
+ if (listingParams.limit === 0)
144
+ return { files: [], directories: [] };
145
+ const [listed, nextQuery, apiResponse] = (await bucketInstance.getFiles(listingQuery(name, listingParams)));
146
+ const skipped = (itemName) => itemName === name || itemName === listingParams.startAfter;
147
+ const files = listed.filter(file => !file.name.endsWith('.folderPlaceholder') && !skipped(file.name));
148
+ const prefixes = (apiResponse?.prefixes ?? []).filter(item => !skipped(item));
149
+ const next = nextQuery?.pageToken;
150
+ return {
151
+ ...(next === undefined ? {} : { next }),
152
+ files: files.map((file) => {
153
+ return {
154
+ name: file.name,
155
+ // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
156
+ size: file.metadata.size ? parseInt(file.metadata.size) : 0,
157
+ lastModified: new Date(file.metadata.updated)
158
+ };
159
+ }),
160
+ directories: prefixes.map(i => {
161
+ const object = {
162
+ bucket,
163
+ name: i
164
+ };
165
+ return object;
166
+ })
167
+ };
168
+ };
169
+ // STO-10, CD10
170
+ const createDirectory = async (reference) => {
171
+ const bucketInstance = getDirectoryBucket(reference, { allowRoot: false });
172
+ const file = bucketInstance.file(`${reference.name}.folderPlaceholder`);
173
+ await file.save('');
174
+ };
175
+ // STO-11, GD7, GF29
176
+ const deleteDirectory = async (reference) => {
177
+ const bucketInstance = getDirectoryBucket(reference, { allowRoot: false });
178
+ let pageToken;
179
+ do {
180
+ const [files, nextQuery] = await bucketInstance.getFiles(everyDepthPage(reference.name, pageToken));
181
+ await deleteAtMost(DELETE_CONCURRENCY, files);
182
+ pageToken = nextQuery?.pageToken;
183
+ } while (pageToken !== undefined);
184
+ };
185
+ // STO-12, GD7, GF22
186
+ const moveDirectory = async (reference, newName) => {
187
+ const bucketInstance = getDirectoryBucket(reference, { allowRoot: false });
188
+ requireDirectoryName({ bucket: reference.bucket, name: newName }, { allowRoot: false });
189
+ const { name } = reference;
190
+ const [files] = await bucketInstance.getFiles({ prefix: name });
191
+ for (const file of files)
192
+ await file.move(newName + file.name.slice(name.length));
193
+ };
194
+ return {
195
+ getObject,
196
+ getPublicURL,
197
+ getSignedURL,
198
+ uploadObject,
199
+ moveObject,
200
+ deleteObject,
201
+ listDirectory,
202
+ createDirectory,
203
+ deleteDirectory,
204
+ moveDirectory
205
+ };
206
+ }
207
+ });
package/package.json CHANGED
@@ -1,31 +1,34 @@
1
1
  {
2
2
  "name": "@genoacms/adapter-gcp",
3
- "version": "0.8.2",
3
+ "version": "0.10.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",
12
13
  "email": "filip.holcik.official@gmail.com"
13
14
  },
14
- "license": "ISC",
15
+ "license": "MIT",
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.3",
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.3"
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;