@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.
- package/dist/authentication/identity-platform/descriptor.d.ts +18 -0
- package/dist/authentication/identity-platform/descriptor.js +14 -0
- package/dist/authentication/identity-platform/runtime.d.ts +4 -0
- package/dist/authentication/identity-platform/runtime.js +140 -0
- package/dist/database/descriptor.d.ts +16 -0
- package/dist/database/descriptor.js +7 -0
- package/dist/database/runtime.d.ts +5 -0
- package/dist/database/runtime.js +68 -0
- package/dist/deployment/archive.d.ts +4 -0
- package/dist/deployment/archive.js +46 -0
- package/dist/deployment/descriptor.d.ts +24 -0
- package/dist/deployment/descriptor.js +20 -0
- package/dist/deployment/functions.d.ts +16 -0
- package/dist/deployment/functions.js +66 -0
- package/dist/deployment/procedure.d.ts +4 -0
- package/dist/deployment/procedure.js +16 -0
- package/dist/deployment/settings.d.ts +22 -0
- package/dist/deployment/settings.js +58 -0
- package/dist/secrets/descriptor.d.ts +14 -0
- package/dist/secrets/descriptor.js +7 -0
- package/dist/secrets/runtime.d.ts +14 -0
- package/dist/secrets/runtime.js +147 -0
- package/dist/shared/serviceAccount.d.ts +16 -0
- package/dist/shared/serviceAccount.js +12 -0
- package/dist/storage/descriptor.d.ts +14 -0
- package/dist/storage/descriptor.js +7 -0
- package/dist/storage/runtime.d.ts +8 -0
- package/dist/storage/runtime.js +178 -0
- package/package.json +46 -27
- package/deployment/snippets/index.js +0 -8
- package/dist/config.d.ts +0 -2
- package/dist/config.js +0 -2
- package/dist/services/authorization/index.d.ts +0 -3
- package/dist/services/authorization/index.js +0 -24
- package/dist/services/database/index.d.ts +0 -7
- package/dist/services/database/index.js +0 -63
- package/dist/services/deployment/deploy.d.ts +0 -2
- package/dist/services/deployment/deploy.js +0 -115
- package/dist/services/deployment/index.d.ts +0 -4
- package/dist/services/deployment/index.js +0 -6
- package/dist/services/storage/index.d.ts +0 -12
- package/dist/services/storage/index.js +0 -87
- package/dist/services/storage/storage.d.ts +0 -3
- package/dist/services/storage/storage.js +0 -20
- package/src/config.ts +0 -4
- package/src/genoa.config.d.ts +0 -51
- package/src/services/authorization/index.ts +0 -28
- package/src/services/database/index.ts +0 -88
- package/src/services/deployment/deploy.ts +0 -125
- package/src/services/deployment/index.ts +0 -13
- package/src/services/storage/index.ts +0 -119
- package/src/services/storage/storage.ts +0 -27
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
import { createReadStream, createWriteStream } from 'node:fs';
|
|
2
|
-
import { resolve, dirname, basename } from 'node:path';
|
|
3
|
-
import { fileURLToPath } from 'node:url';
|
|
4
|
-
import { v2 } from '@google-cloud/functions';
|
|
5
|
-
import archiver from 'archiver';
|
|
6
|
-
import { getProvider } from '@genoacms/cloudabstraction';
|
|
7
|
-
const ADAPTER_PATH = '@genoacms/adapter-gcp/deployment';
|
|
8
|
-
const { FunctionServiceClient } = v2;
|
|
9
|
-
const provider = getProvider('deployment', ADAPTER_PATH);
|
|
10
|
-
const functionsClient = new FunctionServiceClient({
|
|
11
|
-
credentials: provider.credentials
|
|
12
|
-
});
|
|
13
|
-
const { projectId, region, functionName } = provider;
|
|
14
|
-
const currentDir = dirname(fileURLToPath(import.meta.url));
|
|
15
|
-
async function createZip(source, injectPaths, ignorePaths, out) {
|
|
16
|
-
await new Promise((resolve, reject) => {
|
|
17
|
-
const output = createWriteStream(out);
|
|
18
|
-
const archive = archiver('zip', { zlib: { level: 9 } });
|
|
19
|
-
output.on('close', () => {
|
|
20
|
-
resolve();
|
|
21
|
-
});
|
|
22
|
-
archive.on('error', (err) => {
|
|
23
|
-
reject(err);
|
|
24
|
-
});
|
|
25
|
-
archive.pipe(output);
|
|
26
|
-
archive.glob(source, { ignore: ignorePaths, follow: true });
|
|
27
|
-
for (const path of injectPaths) {
|
|
28
|
-
archive.file(path, { name: basename(path) });
|
|
29
|
-
}
|
|
30
|
-
archive.finalize();
|
|
31
|
-
});
|
|
32
|
-
}
|
|
33
|
-
async function uploadSource(sourceArchivePath) {
|
|
34
|
-
const location = functionsClient.locationPath(projectId, region);
|
|
35
|
-
const [urlResponse] = await functionsClient.generateUploadUrl({ parent: location });
|
|
36
|
-
const uploadUrl = urlResponse.uploadUrl;
|
|
37
|
-
const storageSource = urlResponse.storageSource;
|
|
38
|
-
if (!uploadUrl || !storageSource)
|
|
39
|
-
throw new Error('Upload URL not found');
|
|
40
|
-
const sourceArchiveStream = createReadStream(sourceArchivePath);
|
|
41
|
-
await fetch(uploadUrl, {
|
|
42
|
-
method: 'PUT',
|
|
43
|
-
// @ts-expect-error: invalid typings
|
|
44
|
-
body: sourceArchiveStream,
|
|
45
|
-
duplex: 'half',
|
|
46
|
-
headers: {
|
|
47
|
-
'Content-Type': 'application/zip'
|
|
48
|
-
}
|
|
49
|
-
});
|
|
50
|
-
return storageSource;
|
|
51
|
-
}
|
|
52
|
-
async function deployFunction(functionName, storageSource) {
|
|
53
|
-
const location = functionsClient.locationPath(projectId, region);
|
|
54
|
-
const name = functionsClient.functionPath(projectId, region, functionName);
|
|
55
|
-
let isFunctionExisting;
|
|
56
|
-
try {
|
|
57
|
-
await functionsClient.getFunction({ name });
|
|
58
|
-
isFunctionExisting = true;
|
|
59
|
-
}
|
|
60
|
-
catch (error) {
|
|
61
|
-
isFunctionExisting = false;
|
|
62
|
-
}
|
|
63
|
-
const operationParams = {
|
|
64
|
-
functionId: functionName,
|
|
65
|
-
parent: location,
|
|
66
|
-
function: {
|
|
67
|
-
name,
|
|
68
|
-
buildConfig: {
|
|
69
|
-
entryPoint: 'genoacms',
|
|
70
|
-
runtime: 'nodejs20',
|
|
71
|
-
source: {
|
|
72
|
-
storageSource
|
|
73
|
-
}
|
|
74
|
-
},
|
|
75
|
-
serviceConfig: {
|
|
76
|
-
minInstanceCount: 0,
|
|
77
|
-
maxInstanceCount: 1,
|
|
78
|
-
ingressSettings: 1, // ALLOW_ALL
|
|
79
|
-
environmentVariables: {
|
|
80
|
-
NODE_ENV: 'production'
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
};
|
|
85
|
-
let response;
|
|
86
|
-
if (isFunctionExisting) {
|
|
87
|
-
[response] = await functionsClient.updateFunction(operationParams);
|
|
88
|
-
}
|
|
89
|
-
else {
|
|
90
|
-
[response] = await functionsClient.createFunction(operationParams);
|
|
91
|
-
}
|
|
92
|
-
console.log(response);
|
|
93
|
-
}
|
|
94
|
-
async function deploy() {
|
|
95
|
-
const buildDirectoryPath = '**';
|
|
96
|
-
const buildArchivePath = resolve(currentDir, '../../../../../../.genoacms/build.zip');
|
|
97
|
-
const functionEntryScriptPath = resolve(currentDir, '../../../deployment/snippets/index.js');
|
|
98
|
-
const builderScriptPath = resolve(currentDir, '../../../deployment/snippets/build.js');
|
|
99
|
-
const ignoreArchivePaths = [
|
|
100
|
-
'node_modules/**',
|
|
101
|
-
'.git/**',
|
|
102
|
-
'.github/**',
|
|
103
|
-
'.gitignore',
|
|
104
|
-
'.genoacms/**',
|
|
105
|
-
'build/**'
|
|
106
|
-
];
|
|
107
|
-
const injectArchivePaths = [
|
|
108
|
-
functionEntryScriptPath,
|
|
109
|
-
builderScriptPath
|
|
110
|
-
];
|
|
111
|
-
await createZip(buildDirectoryPath, injectArchivePaths, ignoreArchivePaths, buildArchivePath);
|
|
112
|
-
const functionStorageSource = await uploadSource(buildArchivePath);
|
|
113
|
-
await deployFunction(functionName ?? 'genoacms', functionStorageSource);
|
|
114
|
-
}
|
|
115
|
-
export default deploy;
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import type { Adapter } from '@genoacms/cloudabstraction/storage';
|
|
2
|
-
declare const getObject: Adapter['getObject'];
|
|
3
|
-
declare const getPublicURL: Adapter['getPublicURL'];
|
|
4
|
-
declare const getSignedURL: Adapter['getSignedURL'];
|
|
5
|
-
declare const uploadObject: Adapter['uploadObject'];
|
|
6
|
-
declare const moveObject: Adapter['moveObject'];
|
|
7
|
-
declare const deleteObject: Adapter['deleteObject'];
|
|
8
|
-
declare const listDirectory: Adapter['listDirectory'];
|
|
9
|
-
declare const createDirectory: Adapter['createDirectory'];
|
|
10
|
-
declare const deleteDirectory: Adapter['deleteDirectory'];
|
|
11
|
-
declare const moveDirectory: Adapter['moveDirectory'];
|
|
12
|
-
export { getObject, getPublicURL, getSignedURL, uploadObject, moveObject, deleteObject, listDirectory, createDirectory, deleteDirectory, moveDirectory };
|
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
import { getBucket } from './storage.js';
|
|
2
|
-
const getObject = async ({ bucket, name }) => {
|
|
3
|
-
const bucketInstance = getBucket(bucket);
|
|
4
|
-
const file = bucketInstance.file(name);
|
|
5
|
-
return {
|
|
6
|
-
data: file.createReadStream()
|
|
7
|
-
};
|
|
8
|
-
};
|
|
9
|
-
const getPublicURL = async ({ bucket, name }) => {
|
|
10
|
-
const bucketInstance = getBucket(bucket);
|
|
11
|
-
const file = bucketInstance.file(name);
|
|
12
|
-
return file.publicUrl();
|
|
13
|
-
};
|
|
14
|
-
const getSignedURL = async ({ bucket, name }, expires) => {
|
|
15
|
-
const bucketInstance = getBucket(bucket);
|
|
16
|
-
const file = bucketInstance.file(name);
|
|
17
|
-
const [url] = await file.getSignedUrl({
|
|
18
|
-
action: 'read',
|
|
19
|
-
expires
|
|
20
|
-
});
|
|
21
|
-
return url;
|
|
22
|
-
};
|
|
23
|
-
const uploadObject = async ({ bucket, name }, stream, options) => {
|
|
24
|
-
const bucketInstance = getBucket(bucket);
|
|
25
|
-
const file = bucketInstance.file(name);
|
|
26
|
-
await file.save(stream, options);
|
|
27
|
-
};
|
|
28
|
-
const moveObject = async ({ bucket, name }, newName) => {
|
|
29
|
-
const bucketInstance = getBucket(bucket);
|
|
30
|
-
const file = bucketInstance.file(name);
|
|
31
|
-
await file.move(newName);
|
|
32
|
-
};
|
|
33
|
-
const deleteObject = async ({ bucket, name }) => {
|
|
34
|
-
const bucketInstance = getBucket(bucket);
|
|
35
|
-
const file = bucketInstance.file(name);
|
|
36
|
-
await file.delete();
|
|
37
|
-
};
|
|
38
|
-
const listDirectory = async ({ bucket, name }, listingParams = {}) => {
|
|
39
|
-
const bucketInstance = getBucket(bucket);
|
|
40
|
-
const options = {
|
|
41
|
-
autoPaginate: false,
|
|
42
|
-
prefix: name,
|
|
43
|
-
maxResults: listingParams?.limit,
|
|
44
|
-
startOffset: listingParams?.startAfter,
|
|
45
|
-
delimiter: '/'
|
|
46
|
-
};
|
|
47
|
-
let [files, , apiResponse] = (await bucketInstance.getFiles(options));
|
|
48
|
-
files = files.filter((file) => !file.name.endsWith('.folderPlaceholder'));
|
|
49
|
-
return {
|
|
50
|
-
files: files.filter(f => f.name !== name).map((file) => {
|
|
51
|
-
return {
|
|
52
|
-
name: file.name,
|
|
53
|
-
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
|
|
54
|
-
size: file.metadata.size ? parseInt(file.metadata.size) : 0,
|
|
55
|
-
lastModified: new Date(file.metadata.updated)
|
|
56
|
-
};
|
|
57
|
-
}),
|
|
58
|
-
directories: (apiResponse?.prefixes ?? []).filter((item) => item !== name).map(i => {
|
|
59
|
-
const object = {
|
|
60
|
-
bucket,
|
|
61
|
-
name: i
|
|
62
|
-
};
|
|
63
|
-
return object;
|
|
64
|
-
})
|
|
65
|
-
};
|
|
66
|
-
};
|
|
67
|
-
const createDirectory = async ({ bucket, name }) => {
|
|
68
|
-
const bucketInstance = getBucket(bucket);
|
|
69
|
-
const file = bucketInstance.file(`${name}/.folderPlaceholder`);
|
|
70
|
-
await file.save('');
|
|
71
|
-
};
|
|
72
|
-
const deleteDirectory = async ({ bucket, name }) => {
|
|
73
|
-
const bucketInstance = getBucket(bucket);
|
|
74
|
-
const [files] = await bucketInstance.getFiles({ prefix: name });
|
|
75
|
-
const deletePromises = files.map(async (file) => await file.delete());
|
|
76
|
-
await Promise.all(deletePromises);
|
|
77
|
-
};
|
|
78
|
-
const moveDirectory = async ({ bucket, name }, newName) => {
|
|
79
|
-
const bucketInstance = getBucket(bucket);
|
|
80
|
-
const [files] = await bucketInstance.getFiles({ prefix: name });
|
|
81
|
-
const movePromises = files.map(async (file) => {
|
|
82
|
-
const newFileName = file.name.replace(name, newName);
|
|
83
|
-
await file.move(newFileName);
|
|
84
|
-
});
|
|
85
|
-
await Promise.all(movePromises);
|
|
86
|
-
};
|
|
87
|
-
export { getObject, getPublicURL, getSignedURL, uploadObject, moveObject, deleteObject, listDirectory, createDirectory, deleteDirectory, moveDirectory };
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { Storage } from '@google-cloud/storage';
|
|
2
|
-
import { getProvider } from '@genoacms/cloudabstraction';
|
|
3
|
-
import config from '../../config.js';
|
|
4
|
-
const ADAPTER_PATH = '@genoacms/adapter-gcp/storage';
|
|
5
|
-
const provider = getProvider('storage', ADAPTER_PATH);
|
|
6
|
-
const storage = new Storage({
|
|
7
|
-
credentials: provider.credentials
|
|
8
|
-
});
|
|
9
|
-
const hasBucket = (name) => {
|
|
10
|
-
const has = config.storage.buckets.find((bucket) => bucket.name === name &&
|
|
11
|
-
bucket.providerName === provider.name);
|
|
12
|
-
return !!has;
|
|
13
|
-
};
|
|
14
|
-
const getBucket = (name) => {
|
|
15
|
-
if (!hasBucket(name))
|
|
16
|
-
throw new Error('bucket-unregistered');
|
|
17
|
-
const bucket = storage.bucket(name);
|
|
18
|
-
return bucket;
|
|
19
|
-
};
|
|
20
|
-
export { getBucket };
|
package/src/config.ts
DELETED
package/src/genoa.config.d.ts
DELETED
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
import type { Config as ConfigG } from '@genoacms/cloudabstraction/src/genoa.config.js'
|
|
2
|
-
import type { AuthorizationProvider as AuthorizationProviderG } from '@genoacms/cloudabstraction/authorization'
|
|
3
|
-
import type { DatabaseProvider as DatabaseProviderG } from '@genoacms/cloudabstraction/database'
|
|
4
|
-
import type { DeploymentProvider as DeploymentProviderG } from '@genoacms/cloudabstraction/deployment'
|
|
5
|
-
import type { StorageProvider as StorageProviderG } from '@genoacms/cloudabstraction/storage'
|
|
6
|
-
|
|
7
|
-
interface Credentials {
|
|
8
|
-
type: string
|
|
9
|
-
'project_id': string
|
|
10
|
-
'private_key_id': string
|
|
11
|
-
'private_key': string
|
|
12
|
-
'client_email': string
|
|
13
|
-
'client_id': string
|
|
14
|
-
'auth_uri': string
|
|
15
|
-
'token_uri': string
|
|
16
|
-
'auth_provider_x509_cert_url': string
|
|
17
|
-
'client_x509_cert_url': string
|
|
18
|
-
'universe_domain': string
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
interface AuthorizationProvider extends AuthorizationProviderG {
|
|
22
|
-
projectId: string
|
|
23
|
-
credentials: Credentials
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
interface DatabaseProvider extends DatabaseProviderG {
|
|
27
|
-
projectId: string
|
|
28
|
-
credentials: Credentials
|
|
29
|
-
databaseId: string
|
|
30
|
-
region: string
|
|
31
|
-
}
|
|
32
|
-
interface DeploymentProvider extends DeploymentProviderG {
|
|
33
|
-
projectId: string
|
|
34
|
-
credentials: Credentials
|
|
35
|
-
region: string
|
|
36
|
-
functionName: string
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
interface StorageProvider extends StorageProviderG {
|
|
40
|
-
credentials: Credentials
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
interface Config extends ConfigG<object, AuthorizationProvider, DatabaseProvider, StorageProvider> {}
|
|
44
|
-
|
|
45
|
-
export type {
|
|
46
|
-
Config,
|
|
47
|
-
AuthorizationProvider,
|
|
48
|
-
DatabaseProvider,
|
|
49
|
-
DeploymentProvider,
|
|
50
|
-
StorageProvider
|
|
51
|
-
}
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import type { Adapter } from '@genoacms/cloudabstraction/authorization'
|
|
2
|
-
import type { AuthorizationProvider } from '../../genoa.config.js'
|
|
3
|
-
import { getProvider } from '@genoacms/cloudabstraction'
|
|
4
|
-
import { ProjectsClient } from '@google-cloud/resource-manager'
|
|
5
|
-
|
|
6
|
-
const ADAPTER_PATH = '@genoacms/adapter-gcp/authorization'
|
|
7
|
-
const provider = getProvider('authorization', ADAPTER_PATH) as AuthorizationProvider
|
|
8
|
-
const projectId = provider.projectId
|
|
9
|
-
const resourceManager = new ProjectsClient({
|
|
10
|
-
projectId,
|
|
11
|
-
credentials: provider.credentials
|
|
12
|
-
})
|
|
13
|
-
|
|
14
|
-
const isEmailAdmins: Adapter.isEmailAdmins = async (email: string) => {
|
|
15
|
-
const resource = `projects/${projectId}`
|
|
16
|
-
const role = resource + '/roles/genoacms'
|
|
17
|
-
const data = await resourceManager.getIamPolicy({ resource })
|
|
18
|
-
const policy = data[0]
|
|
19
|
-
if ((policy.bindings) == null) throw new Error('no-bindings')
|
|
20
|
-
const adminRole = policy.bindings.find(binding => binding.role === role)
|
|
21
|
-
if (adminRole == null) throw new Error('no-admin-role')
|
|
22
|
-
if (adminRole.members == null) throw new Error('no-principals')
|
|
23
|
-
return adminRole.members.includes(`user:${email}`)
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export {
|
|
27
|
-
isEmailAdmins
|
|
28
|
-
}
|
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
Adapter,
|
|
3
|
-
CollectionSnapshot,
|
|
4
|
-
Document,
|
|
5
|
-
DocumentReference,
|
|
6
|
-
DocumentSnapshot,
|
|
7
|
-
UpdateSnapshot
|
|
8
|
-
} from '@genoacms/cloudabstraction/database'
|
|
9
|
-
import type {
|
|
10
|
-
DatabaseProvider
|
|
11
|
-
} from '../../genoa.config.js'
|
|
12
|
-
import { Firestore } from '@google-cloud/firestore'
|
|
13
|
-
import { getProvider } from '@genoacms/cloudabstraction'
|
|
14
|
-
|
|
15
|
-
const ADAPTER_PATH = '@genoacms/adapter-gcp/database'
|
|
16
|
-
const provider = getProvider('database', ADAPTER_PATH) as DatabaseProvider
|
|
17
|
-
const firestore = new Firestore({
|
|
18
|
-
credentials: provider.credentials,
|
|
19
|
-
databaseId: provider.databaseId,
|
|
20
|
-
projectId: provider.projectId
|
|
21
|
-
})
|
|
22
|
-
|
|
23
|
-
const createDocument: Adapter['createDocument'] = async (reference, data) => {
|
|
24
|
-
const document = await firestore.collection(reference.name).add(data)
|
|
25
|
-
const documentReference: DocumentReference<typeof reference> = {
|
|
26
|
-
collection: reference,
|
|
27
|
-
id: document.id
|
|
28
|
-
}
|
|
29
|
-
return {
|
|
30
|
-
reference: documentReference,
|
|
31
|
-
data
|
|
32
|
-
} satisfies DocumentSnapshot<typeof reference>
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const getCollection: Adapter['getCollection'] = async (reference) => {
|
|
36
|
-
const collection = await firestore.collection(reference.name).get()
|
|
37
|
-
const documents: CollectionSnapshot<typeof reference> = []
|
|
38
|
-
|
|
39
|
-
collection.forEach(document => {
|
|
40
|
-
const documentData: Document<typeof reference.schema> = {}
|
|
41
|
-
Object.keys(reference.schema.properties).forEach(key => {
|
|
42
|
-
documentData[key] = document.get(key)
|
|
43
|
-
})
|
|
44
|
-
|
|
45
|
-
documents.push({
|
|
46
|
-
reference: {
|
|
47
|
-
collection: reference,
|
|
48
|
-
id: document.id
|
|
49
|
-
},
|
|
50
|
-
data: document.data() as Document<typeof reference.schema>
|
|
51
|
-
})
|
|
52
|
-
})
|
|
53
|
-
return documents
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const getDocument: Adapter['getDocument'] = async ({ collection, id }) => {
|
|
57
|
-
const document = await firestore.collection(collection.name).doc(id).get()
|
|
58
|
-
if (!document.exists) return undefined
|
|
59
|
-
const documentReference: DocumentReference<typeof collection> = {
|
|
60
|
-
collection,
|
|
61
|
-
id
|
|
62
|
-
}
|
|
63
|
-
const documentSnapshot: DocumentSnapshot<typeof collection> = {
|
|
64
|
-
reference: documentReference,
|
|
65
|
-
data: document.data() as Document<typeof collection>
|
|
66
|
-
}
|
|
67
|
-
return documentSnapshot
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
const updateDocument: Adapter['updateDocument'] = async (reference, document) => {
|
|
71
|
-
await firestore.collection(reference.collection.name).doc(reference.id).update(document)
|
|
72
|
-
return {
|
|
73
|
-
reference,
|
|
74
|
-
data: document
|
|
75
|
-
} satisfies UpdateSnapshot<typeof reference.collection>
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const deleteDocument: Adapter['deleteDocument'] = async (reference) => {
|
|
79
|
-
await firestore.collection(reference.collection.name).doc(reference.id).delete()
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
export {
|
|
83
|
-
createDocument,
|
|
84
|
-
getDocument,
|
|
85
|
-
getCollection,
|
|
86
|
-
updateDocument,
|
|
87
|
-
deleteDocument
|
|
88
|
-
}
|
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
import type { google } from '@google-cloud/functions/build/protos/protos.js'
|
|
2
|
-
import { type DeploymentProvider } from '../../genoa.config.js'
|
|
3
|
-
import { createReadStream, createWriteStream } from 'node:fs'
|
|
4
|
-
import { resolve, dirname, basename } from 'node:path'
|
|
5
|
-
import { fileURLToPath } from 'node:url'
|
|
6
|
-
import { v2 } from '@google-cloud/functions'
|
|
7
|
-
import archiver from 'archiver'
|
|
8
|
-
import { getProvider } from '@genoacms/cloudabstraction'
|
|
9
|
-
type IStorageSource = google.cloud.functions.v2.IStorageSource
|
|
10
|
-
|
|
11
|
-
const ADAPTER_PATH = '@genoacms/adapter-gcp/deployment'
|
|
12
|
-
const { FunctionServiceClient } = v2
|
|
13
|
-
const provider = getProvider('deployment', ADAPTER_PATH) as DeploymentProvider
|
|
14
|
-
const functionsClient = new FunctionServiceClient({
|
|
15
|
-
credentials: provider.credentials
|
|
16
|
-
})
|
|
17
|
-
const { projectId, region, functionName } = provider
|
|
18
|
-
|
|
19
|
-
const currentDir = dirname(fileURLToPath(import.meta.url))
|
|
20
|
-
|
|
21
|
-
async function createZip (source: string, injectPaths: string[], ignorePaths: string[], out: string): Promise<void> {
|
|
22
|
-
await new Promise<void>((resolve, reject) => {
|
|
23
|
-
const output = createWriteStream(out)
|
|
24
|
-
const archive = archiver('zip', { zlib: { level: 9 } })
|
|
25
|
-
|
|
26
|
-
output.on('close', () => {
|
|
27
|
-
resolve()
|
|
28
|
-
})
|
|
29
|
-
|
|
30
|
-
archive.on('error', (err) => {
|
|
31
|
-
reject(err)
|
|
32
|
-
})
|
|
33
|
-
|
|
34
|
-
archive.pipe(output)
|
|
35
|
-
archive.glob(source, { ignore: ignorePaths, follow: true })
|
|
36
|
-
for (const path of injectPaths) {
|
|
37
|
-
archive.file(path, { name: basename(path) })
|
|
38
|
-
}
|
|
39
|
-
archive.finalize()
|
|
40
|
-
})
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
async function uploadSource (sourceArchivePath: string): Promise<IStorageSource> {
|
|
44
|
-
const location = functionsClient.locationPath(projectId, region)
|
|
45
|
-
const [urlResponse] = await functionsClient.generateUploadUrl({ parent: location })
|
|
46
|
-
const uploadUrl = urlResponse.uploadUrl
|
|
47
|
-
const storageSource = urlResponse.storageSource
|
|
48
|
-
if (!uploadUrl || !storageSource) throw new Error('Upload URL not found')
|
|
49
|
-
const sourceArchiveStream = createReadStream(sourceArchivePath)
|
|
50
|
-
await fetch(uploadUrl, {
|
|
51
|
-
method: 'PUT',
|
|
52
|
-
// @ts-expect-error: invalid typings
|
|
53
|
-
body: sourceArchiveStream,
|
|
54
|
-
duplex: 'half',
|
|
55
|
-
headers: {
|
|
56
|
-
'Content-Type': 'application/zip'
|
|
57
|
-
}
|
|
58
|
-
})
|
|
59
|
-
return storageSource
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
async function deployFunction (functionName: string, storageSource: IStorageSource): Promise<void> {
|
|
63
|
-
const location = functionsClient.locationPath(projectId, region)
|
|
64
|
-
const name = functionsClient.functionPath(projectId, region, functionName)
|
|
65
|
-
let isFunctionExisting: boolean
|
|
66
|
-
try {
|
|
67
|
-
await functionsClient.getFunction({ name })
|
|
68
|
-
isFunctionExisting = true
|
|
69
|
-
} catch (error) {
|
|
70
|
-
isFunctionExisting = false
|
|
71
|
-
}
|
|
72
|
-
const operationParams = {
|
|
73
|
-
functionId: functionName,
|
|
74
|
-
parent: location,
|
|
75
|
-
function: {
|
|
76
|
-
name,
|
|
77
|
-
buildConfig: {
|
|
78
|
-
entryPoint: 'genoacms',
|
|
79
|
-
runtime: 'nodejs20',
|
|
80
|
-
source: {
|
|
81
|
-
storageSource
|
|
82
|
-
}
|
|
83
|
-
},
|
|
84
|
-
serviceConfig: {
|
|
85
|
-
minInstanceCount: 0,
|
|
86
|
-
maxInstanceCount: 1,
|
|
87
|
-
ingressSettings: 1, // ALLOW_ALL
|
|
88
|
-
environmentVariables: {
|
|
89
|
-
NODE_ENV: 'production'
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
let response
|
|
95
|
-
if (isFunctionExisting) {
|
|
96
|
-
[response] = await functionsClient.updateFunction(operationParams)
|
|
97
|
-
} else {
|
|
98
|
-
[response] = await functionsClient.createFunction(operationParams)
|
|
99
|
-
}
|
|
100
|
-
console.log(response)
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
async function deploy (): Promise<void> {
|
|
104
|
-
const buildDirectoryPath = '**'
|
|
105
|
-
const buildArchivePath = resolve(currentDir, '../../../../../../.genoacms/build.zip')
|
|
106
|
-
const functionEntryScriptPath = resolve(currentDir, '../../../deployment/snippets/index.js')
|
|
107
|
-
const builderScriptPath = resolve(currentDir, '../../../deployment/snippets/build.js')
|
|
108
|
-
const ignoreArchivePaths = [
|
|
109
|
-
'node_modules/**',
|
|
110
|
-
'.git/**',
|
|
111
|
-
'.github/**',
|
|
112
|
-
'.gitignore',
|
|
113
|
-
'.genoacms/**',
|
|
114
|
-
'build/**'
|
|
115
|
-
]
|
|
116
|
-
const injectArchivePaths = [
|
|
117
|
-
functionEntryScriptPath,
|
|
118
|
-
builderScriptPath
|
|
119
|
-
]
|
|
120
|
-
await createZip(buildDirectoryPath, injectArchivePaths, ignoreArchivePaths, buildArchivePath)
|
|
121
|
-
const functionStorageSource = await uploadSource(buildArchivePath)
|
|
122
|
-
await deployFunction(functionName ?? 'genoacms', functionStorageSource)
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
export default deploy
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import type { Adapter } from '@genoacms/cloudabstraction/deployment'
|
|
2
|
-
|
|
3
|
-
const svelteKitAdapter: Adapter['svelteKitAdapter'] = '@genoacms/sveltekit-adapter-cloud-run-functions'
|
|
4
|
-
|
|
5
|
-
const deployProcedure: Adapter['deployProcedure'] = async () => {
|
|
6
|
-
const deploy = (await import('./deploy.js')).default
|
|
7
|
-
await deploy()
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export {
|
|
11
|
-
svelteKitAdapter,
|
|
12
|
-
deployProcedure
|
|
13
|
-
}
|
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
Adapter,
|
|
3
|
-
ObjectReference,
|
|
4
|
-
StorageObject
|
|
5
|
-
} from '@genoacms/cloudabstraction/storage'
|
|
6
|
-
import { type File } from '@google-cloud/storage'
|
|
7
|
-
import { getBucket } from './storage.js'
|
|
8
|
-
|
|
9
|
-
const getObject: Adapter['getObject'] = async ({ bucket, name }) => {
|
|
10
|
-
const bucketInstance = getBucket(bucket)
|
|
11
|
-
const file = bucketInstance.file(name)
|
|
12
|
-
|
|
13
|
-
return {
|
|
14
|
-
data: file.createReadStream()
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
const getPublicURL: Adapter['getPublicURL'] = async ({ bucket, name }) => {
|
|
19
|
-
const bucketInstance = getBucket(bucket)
|
|
20
|
-
const file = bucketInstance.file(name)
|
|
21
|
-
return file.publicUrl()
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const getSignedURL: Adapter['getSignedURL'] = async ({ bucket, name }, expires) => {
|
|
25
|
-
const bucketInstance = getBucket(bucket)
|
|
26
|
-
const file = bucketInstance.file(name)
|
|
27
|
-
const [url] = await file.getSignedUrl({
|
|
28
|
-
action: 'read',
|
|
29
|
-
expires
|
|
30
|
-
})
|
|
31
|
-
return url
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const uploadObject: Adapter['uploadObject'] = async ({ bucket, name }, stream, options) => {
|
|
35
|
-
const bucketInstance = getBucket(bucket)
|
|
36
|
-
const file = bucketInstance.file(name)
|
|
37
|
-
await file.save(stream, options)
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const moveObject: Adapter['moveObject'] = async ({ bucket, name }, newName) => {
|
|
41
|
-
const bucketInstance = getBucket(bucket)
|
|
42
|
-
const file = bucketInstance.file(name)
|
|
43
|
-
await file.move(newName)
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const deleteObject: Adapter['deleteObject'] = async ({ bucket, name }) => {
|
|
47
|
-
const bucketInstance = getBucket(bucket)
|
|
48
|
-
const file = bucketInstance.file(name)
|
|
49
|
-
await file.delete()
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
const listDirectory: Adapter['listDirectory'] = async ({ bucket, name }, listingParams = {}) => {
|
|
53
|
-
const bucketInstance = getBucket(bucket)
|
|
54
|
-
const options = {
|
|
55
|
-
autoPaginate: false,
|
|
56
|
-
prefix: name,
|
|
57
|
-
maxResults: listingParams?.limit,
|
|
58
|
-
startOffset: listingParams?.startAfter,
|
|
59
|
-
delimiter: '/'
|
|
60
|
-
|
|
61
|
-
}
|
|
62
|
-
let [files, , apiResponse] =
|
|
63
|
-
(await bucketInstance.getFiles(options)) as [File[], object, { prefixes: string[] } | undefined]
|
|
64
|
-
files = files.filter((file) => !file.name.endsWith('.folderPlaceholder'))
|
|
65
|
-
|
|
66
|
-
return {
|
|
67
|
-
files: files.filter(f => f.name !== name).map((file) => {
|
|
68
|
-
return {
|
|
69
|
-
name: file.name,
|
|
70
|
-
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
|
|
71
|
-
size: file.metadata.size ? parseInt(file.metadata.size as string) : 0,
|
|
72
|
-
lastModified: new Date(file.metadata.updated as string)
|
|
73
|
-
} satisfies StorageObject
|
|
74
|
-
}),
|
|
75
|
-
directories: (apiResponse?.prefixes ?? []).filter((item) => item !== name).map(i => {
|
|
76
|
-
const object: ObjectReference = {
|
|
77
|
-
bucket,
|
|
78
|
-
name: i
|
|
79
|
-
}
|
|
80
|
-
return object
|
|
81
|
-
})
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
const createDirectory: Adapter['createDirectory'] = async ({ bucket, name }) => {
|
|
86
|
-
const bucketInstance = getBucket(bucket)
|
|
87
|
-
const file = bucketInstance.file(`${name}/.folderPlaceholder`)
|
|
88
|
-
await file.save('')
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
const deleteDirectory: Adapter['deleteDirectory'] = async ({ bucket, name }) => {
|
|
92
|
-
const bucketInstance = getBucket(bucket)
|
|
93
|
-
const [files] = await bucketInstance.getFiles({ prefix: name })
|
|
94
|
-
const deletePromises = files.map(async (file) => await file.delete())
|
|
95
|
-
await Promise.all(deletePromises)
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
const moveDirectory: Adapter['moveDirectory'] = async ({ bucket, name }, newName) => {
|
|
99
|
-
const bucketInstance = getBucket(bucket)
|
|
100
|
-
const [files] = await bucketInstance.getFiles({ prefix: name })
|
|
101
|
-
const movePromises = files.map(async (file) => {
|
|
102
|
-
const newFileName = file.name.replace(name, newName)
|
|
103
|
-
await file.move(newFileName)
|
|
104
|
-
})
|
|
105
|
-
await Promise.all(movePromises)
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
export {
|
|
109
|
-
getObject,
|
|
110
|
-
getPublicURL,
|
|
111
|
-
getSignedURL,
|
|
112
|
-
uploadObject,
|
|
113
|
-
moveObject,
|
|
114
|
-
deleteObject,
|
|
115
|
-
listDirectory,
|
|
116
|
-
createDirectory,
|
|
117
|
-
deleteDirectory,
|
|
118
|
-
moveDirectory
|
|
119
|
-
}
|