@genoacms/adapter-gcp 0.4.2 → 0.5.2-fix.10

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,8 @@
1
+ import { handler } from './build/handler.js'
2
+
3
+ function genoacms (req, res) {
4
+ handler(req, res, undefined)
5
+ }
6
+
7
+ export { genoacms }
8
+
@@ -0,0 +1,2 @@
1
+ declare function deploy(): Promise<void>;
2
+ export default deploy;
@@ -0,0 +1,111 @@
1
+ import config from '../../config.js';
2
+ import { createReadStream, createWriteStream } from 'node:fs';
3
+ import { resolve, dirname, basename } from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+ import { v2 } from '@google-cloud/functions';
6
+ import archiver from 'archiver';
7
+ const { FunctionServiceClient } = v2;
8
+ const functionsClient = new FunctionServiceClient({
9
+ credentials: config.deployment.credentials
10
+ });
11
+ const projectId = config.deployment.projectId;
12
+ const region = config.deployment.region;
13
+ const currentDir = dirname(fileURLToPath(import.meta.url));
14
+ async function createZip(source, injectPaths, ignorePaths, out) {
15
+ await new Promise((resolve, reject) => {
16
+ const output = createWriteStream(out);
17
+ const archive = archiver('zip', { zlib: { level: 9 } });
18
+ output.on('close', () => {
19
+ resolve();
20
+ });
21
+ archive.on('error', (err) => {
22
+ reject(err);
23
+ });
24
+ archive.pipe(output);
25
+ archive.glob(source, { ignore: ignorePaths });
26
+ for (const path of injectPaths) {
27
+ archive.file(path, { name: basename(path) });
28
+ }
29
+ archive.finalize();
30
+ });
31
+ }
32
+ async function uploadSource(sourceArchivePath) {
33
+ const location = functionsClient.locationPath(projectId, region);
34
+ const [urlResponse] = await functionsClient.generateUploadUrl({ parent: location });
35
+ const uploadUrl = urlResponse.uploadUrl;
36
+ const storageSource = urlResponse.storageSource;
37
+ if (!uploadUrl || !storageSource)
38
+ throw new Error('Upload URL not found');
39
+ const sourceArchiveStream = createReadStream(sourceArchivePath);
40
+ await fetch(uploadUrl, {
41
+ method: 'PUT',
42
+ // @ts-expect-error: invalid typings
43
+ body: sourceArchiveStream,
44
+ duplex: 'half',
45
+ headers: {
46
+ 'Content-Type': 'application/zip'
47
+ }
48
+ });
49
+ return storageSource;
50
+ }
51
+ async function deployFunction(functionName, storageSource) {
52
+ const location = functionsClient.locationPath(projectId, region);
53
+ const name = functionsClient.functionPath(projectId, region, functionName);
54
+ let isFunctionExisting;
55
+ try {
56
+ await functionsClient.getFunction({ name });
57
+ isFunctionExisting = true;
58
+ }
59
+ catch (error) {
60
+ isFunctionExisting = false;
61
+ }
62
+ const operationParams = {
63
+ functionId: functionName,
64
+ parent: location,
65
+ function: {
66
+ name,
67
+ buildConfig: {
68
+ entryPoint: 'genoacms',
69
+ runtime: 'nodejs20',
70
+ source: {
71
+ storageSource
72
+ }
73
+ },
74
+ serviceConfig: {
75
+ minInstanceCount: 0,
76
+ maxInstanceCount: 1,
77
+ ingressSettings: 1,
78
+ environmentVariables: {
79
+ NODE_ENV: 'production'
80
+ }
81
+ }
82
+ }
83
+ };
84
+ let response;
85
+ if (isFunctionExisting) {
86
+ [response] = await functionsClient.updateFunction(operationParams);
87
+ }
88
+ else {
89
+ [response] = await functionsClient.createFunction(operationParams);
90
+ }
91
+ console.log(response);
92
+ }
93
+ async function deploy() {
94
+ const buildDirectoryPath = '**';
95
+ const buildArchivePath = resolve(currentDir, '../../../deployment/build.zip');
96
+ const functionEntryScriptPath = resolve(currentDir, '../../../deployment/snippets/index.js');
97
+ const ignoreArchivePaths = [
98
+ 'node_modules/**',
99
+ '.git/**',
100
+ '.github/**',
101
+ '.gitignore',
102
+ 'build/**'
103
+ ];
104
+ const injectArchivePaths = [
105
+ functionEntryScriptPath
106
+ ];
107
+ await createZip(buildDirectoryPath, injectArchivePaths, ignoreArchivePaths, buildArchivePath);
108
+ const functionStorageSource = await uploadSource(buildArchivePath);
109
+ await deployFunction('genoacms', functionStorageSource);
110
+ }
111
+ export default deploy;
@@ -0,0 +1,4 @@
1
+ import type { Adapter } from '@genoacms/cloudabstraction/deployment';
2
+ declare const svelteKitAdapter: Adapter.svelteKitAdapter;
3
+ declare const deployProcedure: Adapter.deployProcedure;
4
+ export { svelteKitAdapter, deployProcedure };
@@ -0,0 +1,6 @@
1
+ const svelteKitAdapter = '@sveltejs/adapter-node';
2
+ const deployProcedure = async () => {
3
+ const deploy = (await import('./deploy.js')).default;
4
+ await deploy();
5
+ };
6
+ export { svelteKitAdapter, deployProcedure };
@@ -1,14 +1,5 @@
1
- import { Storage } from '@google-cloud/storage';
2
- import config from '../../config.js';
3
- const storage = new Storage({
4
- credentials: config.storage.credentials
5
- });
6
- const getBucket = (name) => {
7
- if (!config.storage.buckets.includes(name))
8
- throw new Error('bucket-unregistered');
9
- const bucket = storage.bucket(name);
10
- return bucket;
11
- };
1
+ import { getBucket } from './storage.js';
2
+ import { join } from 'path';
12
3
  const getObject = async ({ bucket, name }) => {
13
4
  const bucketInstance = getBucket(bucket);
14
5
  const file = bucketInstance.file(name);
@@ -21,21 +12,19 @@ const getPublicURL = async ({ bucket, name }) => {
21
12
  const file = bucketInstance.file(name);
22
13
  return file.publicUrl();
23
14
  };
24
- const getSignedURL = async ({ bucket, name }) => {
15
+ const getSignedURL = async ({ bucket, name }, expires) => {
25
16
  const bucketInstance = getBucket(bucket);
26
17
  const file = bucketInstance.file(name);
27
- const expires = new Date();
28
- expires.setTime(expires.getTime() + 60 * 60 * 1_000);
29
18
  const [url] = await file.getSignedUrl({
30
19
  action: 'read',
31
20
  expires
32
21
  });
33
22
  return url;
34
23
  };
35
- const uploadObject = async ({ bucket, name }, stream) => {
24
+ const uploadObject = async ({ bucket, name }, stream, options) => {
36
25
  const bucketInstance = getBucket(bucket);
37
26
  const file = bucketInstance.file(name);
38
- await file.save(stream);
27
+ await file.save(stream, options);
39
28
  };
40
29
  const deleteObject = async ({ bucket, name }) => {
41
30
  const bucketInstance = getBucket(bucket);
@@ -46,7 +35,7 @@ const listDirectory = async ({ bucket, name }, listingParams = {}) => {
46
35
  const bucketInstance = getBucket(bucket);
47
36
  const options = {
48
37
  autoPaginate: false,
49
- prefix: name,
38
+ prefix: join(name, '/'),
50
39
  maxResults: listingParams?.limit,
51
40
  startOffset: listingParams?.startAfter,
52
41
  delimiter: '/'
@@ -62,7 +51,7 @@ const listDirectory = async ({ bucket, name }, listingParams = {}) => {
62
51
  lastModified: new Date(file.metadata.updated)
63
52
  };
64
53
  }),
65
- directories: apiResponse?.prefixes ?? []
54
+ directories: (apiResponse?.prefixes ?? []).filter((item) => item !== name)
66
55
  };
67
56
  };
68
57
  const createDirectory = async ({ bucket, name }) => {
@@ -0,0 +1,3 @@
1
+ import { type Bucket } from '@google-cloud/storage';
2
+ declare const getBucket: (name: string) => Bucket;
3
+ export { getBucket };
@@ -0,0 +1,12 @@
1
+ import config from '../../config.js';
2
+ import { Storage } from '@google-cloud/storage';
3
+ const storage = new Storage({
4
+ credentials: config.storage.credentials
5
+ });
6
+ const getBucket = (name) => {
7
+ if (!config.storage.buckets.includes(name))
8
+ throw new Error('bucket-unregistered');
9
+ const bucket = storage.bucket(name);
10
+ return bucket;
11
+ };
12
+ export { getBucket };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@genoacms/adapter-gcp",
3
- "version": "0.4.2",
3
+ "version": "0.5.2-fix.10",
4
4
  "description": "Implementation of abstraction layer of GenoaCMS for GCP",
5
5
  "repository": {
6
6
  "type": "git",
@@ -17,12 +17,15 @@
17
17
  "homepage": "https://github.com/GenoaCMS/adapter-gcp#readme",
18
18
  "type": "module",
19
19
  "dependencies": {
20
- "@genoacms/cloudabstraction": "0.4.2-fix.2",
20
+ "@genoacms/cloudabstraction": "^0.5.2",
21
21
  "@google-cloud/firestore": "^7.1.0",
22
+ "@google-cloud/functions": "^3.4.0",
22
23
  "@google-cloud/resource-manager": "^5.1.0",
23
- "@google-cloud/storage": "^7.7.0"
24
+ "@google-cloud/storage": "^7.7.0",
25
+ "archiver": "^7.0.0"
24
26
  },
25
27
  "devDependencies": {
28
+ "@types/archiver": "^6.0.2",
26
29
  "@typescript-eslint/eslint-plugin": "^6.9.0",
27
30
  "eslint": "^8.52.0",
28
31
  "eslint-config-standard-with-typescript": "^39.1.1",
@@ -33,8 +36,12 @@
33
36
  "typescript": "^5.2.2",
34
37
  "vitest": "^1.0.4"
35
38
  },
39
+ "peerDependencies": {
40
+ "@sveltejs/adapter-node": "^4.0.1"
41
+ },
36
42
  "files": [
37
43
  "src",
44
+ "deployment",
38
45
  "dist"
39
46
  ],
40
47
  "exports": {
@@ -46,6 +53,10 @@
46
53
  "import": "./dist/services/database/index.js",
47
54
  "types": "./dist/services/database/index.d.ts"
48
55
  },
56
+ "./deployment": {
57
+ "import": "./dist/services/deployment/index.js",
58
+ "types": "./dist/services/deployment/index.d.ts"
59
+ },
49
60
  "./storage": {
50
61
  "import": "./dist/services/storage/index.js",
51
62
  "types": "./dist/services/storage/index.d.ts"
@@ -0,0 +1,120 @@
1
+ import config from '../../config.js'
2
+ import { createReadStream, createWriteStream } from 'node:fs'
3
+ import { resolve, dirname, basename } from 'node:path'
4
+ import { fileURLToPath } from 'node:url'
5
+ import { v2 } from '@google-cloud/functions'
6
+ import archiver from 'archiver'
7
+ import type { google } from '@google-cloud/functions/build/protos/protos.js'
8
+ type IStorageSource = google.cloud.functions.v2.IStorageSource
9
+
10
+ const { FunctionServiceClient } = v2
11
+ const functionsClient = new FunctionServiceClient({
12
+ credentials: config.deployment.credentials
13
+ })
14
+ const projectId = config.deployment.projectId
15
+ const region = config.deployment.region
16
+
17
+ const currentDir = dirname(fileURLToPath(import.meta.url))
18
+
19
+ async function createZip (source: string, injectPaths: string[], ignorePaths: string[], out: string): Promise<void> {
20
+ await new Promise<void>((resolve, reject) => {
21
+ const output = createWriteStream(out)
22
+ const archive = archiver('zip', { zlib: { level: 9 } })
23
+
24
+ output.on('close', () => {
25
+ resolve()
26
+ })
27
+
28
+ archive.on('error', (err) => {
29
+ reject(err)
30
+ })
31
+
32
+ archive.pipe(output)
33
+ archive.glob(source, { ignore: ignorePaths })
34
+ for (const path of injectPaths) {
35
+ archive.file(path, { name: basename(path) })
36
+ }
37
+ archive.finalize()
38
+ })
39
+ }
40
+
41
+ async function uploadSource (sourceArchivePath: string): Promise<IStorageSource> {
42
+ const location = functionsClient.locationPath(projectId, region)
43
+ const [urlResponse] = await functionsClient.generateUploadUrl({ parent: location })
44
+ const uploadUrl = urlResponse.uploadUrl
45
+ const storageSource = urlResponse.storageSource
46
+ if (!uploadUrl || !storageSource) throw new Error('Upload URL not found')
47
+ const sourceArchiveStream = createReadStream(sourceArchivePath)
48
+ await fetch(uploadUrl, {
49
+ method: 'PUT',
50
+ // @ts-expect-error: invalid typings
51
+ body: sourceArchiveStream,
52
+ duplex: 'half',
53
+ headers: {
54
+ 'Content-Type': 'application/zip'
55
+ }
56
+ })
57
+ return storageSource
58
+ }
59
+
60
+ async function deployFunction (functionName: string, storageSource: IStorageSource): Promise<void> {
61
+ const location = functionsClient.locationPath(projectId, region)
62
+ const name = functionsClient.functionPath(projectId, region, functionName)
63
+ let isFunctionExisting: boolean
64
+ try {
65
+ await functionsClient.getFunction({ name })
66
+ isFunctionExisting = true
67
+ } catch (error) {
68
+ isFunctionExisting = false
69
+ }
70
+ const operationParams = {
71
+ functionId: functionName,
72
+ parent: location,
73
+ function: {
74
+ name,
75
+ buildConfig: {
76
+ entryPoint: 'genoacms',
77
+ runtime: 'nodejs20',
78
+ source: {
79
+ storageSource
80
+ }
81
+ },
82
+ serviceConfig: {
83
+ minInstanceCount: 0,
84
+ maxInstanceCount: 1,
85
+ ingressSettings: 1, // ALLOW_ALL
86
+ environmentVariables: {
87
+ NODE_ENV: 'production'
88
+ }
89
+ }
90
+ }
91
+ }
92
+ let response
93
+ if (isFunctionExisting) {
94
+ [response] = await functionsClient.updateFunction(operationParams)
95
+ } else {
96
+ [response] = await functionsClient.createFunction(operationParams)
97
+ }
98
+ console.log(response)
99
+ }
100
+
101
+ async function deploy (): Promise<void> {
102
+ const buildDirectoryPath = '**'
103
+ const buildArchivePath = resolve(currentDir, '../../../deployment/build.zip')
104
+ const functionEntryScriptPath = resolve(currentDir, '../../../deployment/snippets/index.js')
105
+ const ignoreArchivePaths = [
106
+ 'node_modules/**',
107
+ '.git/**',
108
+ '.github/**',
109
+ '.gitignore',
110
+ 'build/**'
111
+ ]
112
+ const injectArchivePaths = [
113
+ functionEntryScriptPath
114
+ ]
115
+ await createZip(buildDirectoryPath, injectArchivePaths, ignoreArchivePaths, buildArchivePath)
116
+ const functionStorageSource = await uploadSource(buildArchivePath)
117
+ await deployFunction('genoacms', functionStorageSource)
118
+ }
119
+
120
+ export default deploy
@@ -0,0 +1,13 @@
1
+ import type { Adapter } from '@genoacms/cloudabstraction/deployment'
2
+
3
+ const svelteKitAdapter: Adapter.svelteKitAdapter = '@sveltejs/adapter-node'
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
+ }
@@ -2,18 +2,9 @@ import type {
2
2
  Adapter,
3
3
  StorageObject
4
4
  } from '@genoacms/cloudabstraction/storage'
5
- import { type Bucket, Storage, type File } from '@google-cloud/storage'
6
- import config from '../../config.js'
7
-
8
- const storage = new Storage({
9
- credentials: config.storage.credentials
10
- })
11
-
12
- const getBucket = (name: string): Bucket => {
13
- if (!config.storage.buckets.includes(name)) throw new Error('bucket-unregistered')
14
- const bucket = storage.bucket(name)
15
- return bucket
16
- }
5
+ import { type File } from '@google-cloud/storage'
6
+ import { getBucket } from './storage.js'
7
+ import { join } from 'path'
17
8
 
18
9
  const getObject: Adapter.getObject = async ({ bucket, name }) => {
19
10
  const bucketInstance = getBucket(bucket)
@@ -30,11 +21,9 @@ const getPublicURL: Adapter.getPublicURL = async ({ bucket, name }) => {
30
21
  return file.publicUrl()
31
22
  }
32
23
 
33
- const getSignedURL: Adapter.getSignedURL = async ({ bucket, name }) => {
24
+ const getSignedURL: Adapter.getSignedURL = async ({ bucket, name }, expires) => {
34
25
  const bucketInstance = getBucket(bucket)
35
26
  const file = bucketInstance.file(name)
36
- const expires = new Date()
37
- expires.setTime(expires.getTime() + 60 * 60 * 1_000)
38
27
  const [url] = await file.getSignedUrl({
39
28
  action: 'read',
40
29
  expires
@@ -42,10 +31,10 @@ const getSignedURL: Adapter.getSignedURL = async ({ bucket, name }) => {
42
31
  return url
43
32
  }
44
33
 
45
- const uploadObject: Adapter.uploadObject = async ({ bucket, name }, stream) => {
34
+ const uploadObject: Adapter.uploadObject = async ({ bucket, name }, stream, options) => {
46
35
  const bucketInstance = getBucket(bucket)
47
36
  const file = bucketInstance.file(name)
48
- await file.save(stream)
37
+ await file.save(stream, options)
49
38
  }
50
39
 
51
40
  const deleteObject: Adapter.deleteObject = async ({ bucket, name }) => {
@@ -58,7 +47,7 @@ const listDirectory: Adapter.listDirectory = async ({ bucket, name }, listingPar
58
47
  const bucketInstance = getBucket(bucket)
59
48
  const options = {
60
49
  autoPaginate: false,
61
- prefix: name,
50
+ prefix: join(name, '/'),
62
51
  maxResults: listingParams?.limit,
63
52
  startOffset: listingParams?.startAfter,
64
53
  delimiter: '/'
@@ -77,7 +66,7 @@ const listDirectory: Adapter.listDirectory = async ({ bucket, name }, listingPar
77
66
  lastModified: new Date(file.metadata.updated as string)
78
67
  } satisfies StorageObject
79
68
  }),
80
- directories: apiResponse?.prefixes ?? []
69
+ directories: (apiResponse?.prefixes ?? []).filter((item) => item !== name)
81
70
  }
82
71
  }
83
72
 
@@ -0,0 +1,16 @@
1
+ import config from '../../config.js'
2
+ import { type Bucket, Storage } from '@google-cloud/storage'
3
+
4
+ const storage = new Storage({
5
+ credentials: config.storage.credentials
6
+ })
7
+
8
+ const getBucket = (name: string): Bucket => {
9
+ if (!config.storage.buckets.includes(name)) throw new Error('bucket-unregistered')
10
+ const bucket = storage.bucket(name)
11
+ return bucket
12
+ }
13
+
14
+ export {
15
+ getBucket
16
+ }