@genoacms/adapter-gcp 0.5.2-fix.2 → 0.5.2-fix.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -1 +1,2 @@
1
- export default function (): Promise<void>;
1
+ declare function deploy(): Promise<void>;
2
+ export default deploy;
@@ -1,8 +1,8 @@
1
1
  import config from '../../config.js';
2
- import { getBucket } from '../storage/storage.js';
3
2
  import { readdir, lstat } from 'node:fs/promises';
4
3
  import { createReadStream, createWriteStream } from 'node:fs';
5
- import { join } from 'node:path';
4
+ import { join, resolve, dirname } from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
6
6
  import { CloudFunctionsServiceClient } from '@google-cloud/functions';
7
7
  import archiver from 'archiver';
8
8
  const functionsClient = new CloudFunctionsServiceClient({
@@ -10,29 +10,37 @@ const functionsClient = new CloudFunctionsServiceClient({
10
10
  });
11
11
  const projectId = config.deployment.projectId;
12
12
  const region = config.deployment.region;
13
+ const { uploadObject, getSignedURL } = await config.storage.adapter;
14
+ function locateFunctionEntryScript() {
15
+ const currentDir = dirname(fileURLToPath(import.meta.url));
16
+ const indexPath = resolve(currentDir, './snippets/index.js');
17
+ return indexPath;
18
+ }
19
+ async function uploadFile(bucketName, filePath, destination) {
20
+ const reference = { bucket: bucketName, name: destination };
21
+ await uploadObject(reference, createReadStream(filePath));
22
+ return await getSignedURL(reference, new Date(Date.now() + 1000 * 60 * 60 * 12));
23
+ }
24
+ async function uploadFileOrDirectory(bucketName, path, prefix = '') {
25
+ const isDirectory = (await lstat(path)).isDirectory();
26
+ if (isDirectory) {
27
+ await uploadDirectory(bucketName, path, prefix);
28
+ }
29
+ else {
30
+ await uploadFile(bucketName, path, prefix);
31
+ }
32
+ }
13
33
  async function uploadDirectory(bucketName, directoryPath, prefix = '') {
14
- const bucket = getBucket(bucketName);
15
34
  const files = await readdir(directoryPath);
35
+ const promises = [];
16
36
  for (const file of files) {
17
37
  const filePath = join(directoryPath, file);
18
38
  const destination = join(prefix, file);
19
- const isFileDirectory = (await lstat(filePath)).isDirectory();
20
- if (isFileDirectory) {
21
- await uploadDirectory(bucketName, filePath, destination);
22
- }
23
- else {
24
- const fileStream = createReadStream(filePath);
25
- const gcsFile = bucket.file(destination);
26
- await new Promise((resolve, reject) => {
27
- fileStream
28
- .pipe(gcsFile.createWriteStream())
29
- .on('error', reject)
30
- .on('finish', resolve);
31
- });
32
- }
39
+ promises.push(uploadFileOrDirectory(bucketName, filePath, destination));
33
40
  }
41
+ await Promise.all(promises);
34
42
  }
35
- async function zipDirectory(source, out) {
43
+ async function createZip(source, injectPaths, ignorePaths, out) {
36
44
  await new Promise((resolve, reject) => {
37
45
  const output = createWriteStream(out);
38
46
  const archive = archiver('zip', { zlib: { level: 9 } });
@@ -43,36 +51,83 @@ async function zipDirectory(source, out) {
43
51
  reject(err);
44
52
  });
45
53
  archive.pipe(output);
46
- archive.directory(source, false);
54
+ archive.glob(source, { ignore: ignorePaths });
55
+ for (const path of injectPaths) {
56
+ archive.append(path, { name: path });
57
+ }
47
58
  archive.finalize();
48
59
  });
49
60
  }
61
+ async function uploadSource(sourceArchivePath) {
62
+ const location = functionsClient.locationPath(projectId, region);
63
+ const [urlResponse] = await functionsClient.generateUploadUrl({ parent: location });
64
+ const uploadUrl = urlResponse.uploadUrl;
65
+ if (!uploadUrl)
66
+ throw new Error('Upload URL not found');
67
+ const sourceArchiveStream = createReadStream(sourceArchivePath);
68
+ await fetch(uploadUrl, {
69
+ method: 'PUT',
70
+ // @ts-expect-error: invalid typings
71
+ body: sourceArchiveStream,
72
+ duplex: 'half',
73
+ headers: {
74
+ 'Content-Type': 'application/zip'
75
+ }
76
+ });
77
+ return uploadUrl;
78
+ }
50
79
  async function deployFunction(functionName, source) {
51
80
  const location = functionsClient.locationPath(projectId, region);
52
81
  const name = functionsClient.cloudFunctionPath(projectId, region, functionName);
53
- const [response] = await functionsClient.createFunction({
82
+ let isFunctionExisting;
83
+ try {
84
+ await functionsClient.getFunction({ name });
85
+ isFunctionExisting = true;
86
+ }
87
+ catch (error) {
88
+ isFunctionExisting = false;
89
+ }
90
+ const operationParams = {
54
91
  location,
55
92
  function: {
56
93
  name,
57
94
  sourceUploadUrl: source,
58
- entryPoint: 'handler',
95
+ entryPoint: 'svelteKitApp',
59
96
  runtime: 'nodejs20',
60
97
  httpsTrigger: {},
61
98
  environmentVariables: {
62
99
  NODE_ENV: 'production'
63
100
  }
64
101
  }
65
- }, {});
66
- console.log(response);
102
+ };
103
+ let response;
104
+ if (isFunctionExisting) {
105
+ [response] = await functionsClient.updateFunction(operationParams, {});
106
+ }
107
+ else {
108
+ [response] = await functionsClient.createFunction(operationParams, {});
109
+ }
110
+ console.log(response, source);
67
111
  }
68
- export default async function () {
112
+ async function deploy() {
69
113
  const bucketName = config.storage.defaultBucket;
70
- const assetsPath = '.genoacms/deployment/static';
71
- const buildArchiveSrc = '.build.zip';
72
- const buildArchiveDest = '.genoacms/deployment/build.zip';
73
- const buildArchiveRef = `gs://${bucketName}/${buildArchiveDest}`;
74
- await zipDirectory('./build', buildArchiveSrc);
75
- await uploadDirectory(bucketName, './static', assetsPath);
76
- await uploadDirectory(bucketName, buildArchiveSrc, buildArchiveDest);
77
- await deployFunction('genoacms', buildArchiveRef);
114
+ const buildDirectoryPath = './*';
115
+ const buildArchivePath = '.build.zip';
116
+ const assetsDirectoryPath = './static';
117
+ const assetsDestPath = '.genoacms/deployment/static';
118
+ const ignoreArchivePaths = [
119
+ 'node_modules',
120
+ '.git',
121
+ '.github',
122
+ '.gitignore',
123
+ 'build'
124
+ ];
125
+ const injectArchivePaths = [
126
+ locateFunctionEntryScript()
127
+ ];
128
+ await createZip(buildDirectoryPath, injectArchivePaths, ignoreArchivePaths, buildArchivePath);
129
+ const uploadUrl = await uploadSource(buildArchivePath);
130
+ await uploadDirectory(bucketName, assetsDirectoryPath, assetsDestPath);
131
+ await deployFunction('genoacms', uploadUrl);
78
132
  }
133
+ export default deploy;
@@ -11,11 +11,9 @@ const getPublicURL = async ({ bucket, name }) => {
11
11
  const file = bucketInstance.file(name);
12
12
  return file.publicUrl();
13
13
  };
14
- const getSignedURL = async ({ bucket, name }) => {
14
+ const getSignedURL = async ({ bucket, name }, expires) => {
15
15
  const bucketInstance = getBucket(bucket);
16
16
  const file = bucketInstance.file(name);
17
- const expires = new Date();
18
- expires.setTime(expires.getTime() + 60 * 60 * 1_000);
19
17
  const [url] = await file.getSignedUrl({
20
18
  action: 'read',
21
19
  expires
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@genoacms/adapter-gcp",
3
- "version": "0.5.2-fix.2",
3
+ "version": "0.5.2-fix.6",
4
4
  "description": "Implementation of abstraction layer of GenoaCMS for GCP",
5
5
  "repository": {
6
6
  "type": "git",
@@ -20,6 +20,7 @@
20
20
  "@genoacms/cloudabstraction": "^0.5.2",
21
21
  "@google-cloud/firestore": "^7.1.0",
22
22
  "@google-cloud/functions": "^3.2.0",
23
+ "@google-cloud/functions-framework": "^3.3.0",
23
24
  "@google-cloud/resource-manager": "^5.1.0",
24
25
  "@google-cloud/storage": "^7.7.0",
25
26
  "archiver": "^7.0.0"
@@ -1,8 +1,8 @@
1
1
  import config from '../../config.js'
2
- import { getBucket } from '../storage/storage.js'
3
2
  import { readdir, lstat } from 'node:fs/promises'
4
3
  import { createReadStream, createWriteStream } from 'node:fs'
5
- import { join } from 'node:path'
4
+ import { join, resolve, dirname } from 'node:path'
5
+ import { fileURLToPath } from 'node:url'
6
6
  import { CloudFunctionsServiceClient } from '@google-cloud/functions'
7
7
  import archiver from 'archiver'
8
8
 
@@ -11,33 +11,41 @@ const functionsClient = new CloudFunctionsServiceClient({
11
11
  })
12
12
  const projectId = config.deployment.projectId
13
13
  const region = config.deployment.region
14
+ const { uploadObject, getSignedURL } = await config.storage.adapter
15
+
16
+ function locateFunctionEntryScript (): string {
17
+ const currentDir = dirname(fileURLToPath(import.meta.url))
18
+ const indexPath = resolve(currentDir, './snippets/index.js')
19
+ return indexPath
20
+ }
21
+ async function uploadFile (bucketName: string, filePath: string, destination: string): Promise<void> {
22
+ const reference = { bucket: bucketName, name: destination }
23
+ await uploadObject(reference, createReadStream(filePath))
24
+ return await getSignedURL(reference, new Date(Date.now() + 1000 * 60 * 60 * 12))
25
+ }
26
+
27
+ async function uploadFileOrDirectory (bucketName: string, path: string, prefix = ''): Promise<void> {
28
+ const isDirectory = (await lstat(path)).isDirectory()
29
+ if (isDirectory) {
30
+ await uploadDirectory(bucketName, path, prefix)
31
+ } else {
32
+ await uploadFile(bucketName, path, prefix)
33
+ }
34
+ }
14
35
 
15
36
  async function uploadDirectory (bucketName: string, directoryPath: string, prefix = ''): Promise<void> {
16
- const bucket = getBucket(bucketName)
17
37
  const files = await readdir(directoryPath)
38
+ const promises = []
18
39
 
19
40
  for (const file of files) {
20
41
  const filePath = join(directoryPath, file)
21
42
  const destination = join(prefix, file)
22
-
23
- const isFileDirectory = (await lstat(filePath)).isDirectory()
24
- if (isFileDirectory) {
25
- await uploadDirectory(bucketName, filePath, destination)
26
- } else {
27
- const fileStream = createReadStream(filePath)
28
- const gcsFile = bucket.file(destination)
29
-
30
- await new Promise((resolve, reject) => {
31
- fileStream
32
- .pipe(gcsFile.createWriteStream())
33
- .on('error', reject)
34
- .on('finish', resolve)
35
- })
36
- }
43
+ promises.push(uploadFileOrDirectory(bucketName, filePath, destination))
37
44
  }
45
+ await Promise.all(promises)
38
46
  }
39
47
 
40
- async function zipDirectory (source: string, out: string): Promise<void> {
48
+ async function createZip (source: string, injectPaths: string[], ignorePaths: string[], out: string): Promise<void> {
41
49
  await new Promise<void>((resolve, reject) => {
42
50
  const output = createWriteStream(out)
43
51
  const archive = archiver('zip', { zlib: { level: 9 } })
@@ -51,38 +59,84 @@ async function zipDirectory (source: string, out: string): Promise<void> {
51
59
  })
52
60
 
53
61
  archive.pipe(output)
54
- archive.directory(source, false)
62
+ archive.glob(source, { ignore: ignorePaths })
63
+ for (const path of injectPaths) {
64
+ archive.append(path, { name: path })
65
+ }
55
66
  archive.finalize()
56
67
  })
57
68
  }
58
69
 
70
+ async function uploadSource (sourceArchivePath: string): Promise<string> {
71
+ const location = functionsClient.locationPath(projectId, region)
72
+ const [urlResponse] = await functionsClient.generateUploadUrl({ parent: location })
73
+ const uploadUrl = urlResponse.uploadUrl
74
+ if (!uploadUrl) throw new Error('Upload URL not found')
75
+ const sourceArchiveStream = createReadStream(sourceArchivePath)
76
+ await fetch(uploadUrl, {
77
+ method: 'PUT',
78
+ // @ts-expect-error: invalid typings
79
+ body: sourceArchiveStream,
80
+ duplex: 'half',
81
+ headers: {
82
+ 'Content-Type': 'application/zip'
83
+ }
84
+ })
85
+ return uploadUrl
86
+ }
87
+
59
88
  async function deployFunction (functionName: string, source: string): Promise<void> {
60
89
  const location = functionsClient.locationPath(projectId, region)
61
90
  const name = functionsClient.cloudFunctionPath(projectId, region, functionName)
62
- const [response] = await functionsClient.createFunction({
91
+ let isFunctionExisting: boolean
92
+ try {
93
+ await functionsClient.getFunction({ name })
94
+ isFunctionExisting = true
95
+ } catch (error) {
96
+ isFunctionExisting = false
97
+ }
98
+ const operationParams = {
63
99
  location,
64
100
  function: {
65
101
  name,
66
102
  sourceUploadUrl: source,
67
- entryPoint: 'handler',
103
+ entryPoint: 'svelteKitApp',
68
104
  runtime: 'nodejs20',
69
105
  httpsTrigger: {},
70
106
  environmentVariables: {
71
107
  NODE_ENV: 'production'
72
108
  }
73
109
  }
74
- }, {})
75
- console.log(response)
110
+ }
111
+ let response
112
+ if (isFunctionExisting) {
113
+ [response] = await functionsClient.updateFunction(operationParams, {})
114
+ } else {
115
+ [response] = await functionsClient.createFunction(operationParams, {})
116
+ }
117
+ console.log(response, source)
76
118
  }
77
119
 
78
- export default async function (): Promise<void> {
120
+ async function deploy (): Promise<void> {
79
121
  const bucketName = config.storage.defaultBucket
80
- const assetsPath = '.genoacms/deployment/static'
81
- const buildArchiveSrc = '.build.zip'
82
- const buildArchiveDest = '.genoacms/deployment/build.zip'
83
- const buildArchiveRef = `gs://${bucketName}/${buildArchiveDest}`
84
- await zipDirectory('./build', buildArchiveSrc)
85
- await uploadDirectory(bucketName, './static', assetsPath)
86
- await uploadDirectory(bucketName, buildArchiveSrc, buildArchiveDest)
87
- await deployFunction('genoacms', buildArchiveRef)
122
+ const buildDirectoryPath = './*'
123
+ const buildArchivePath = '.build.zip'
124
+ const assetsDirectoryPath = './static'
125
+ const assetsDestPath = '.genoacms/deployment/static'
126
+ const ignoreArchivePaths = [
127
+ 'node_modules',
128
+ '.git',
129
+ '.github',
130
+ '.gitignore',
131
+ 'build'
132
+ ]
133
+ const injectArchivePaths = [
134
+ locateFunctionEntryScript()
135
+ ]
136
+ await createZip(buildDirectoryPath, injectArchivePaths, ignoreArchivePaths, buildArchivePath)
137
+ const uploadUrl = await uploadSource(buildArchivePath)
138
+ await uploadDirectory(bucketName, assetsDirectoryPath, assetsDestPath)
139
+ await deployFunction('genoacms', uploadUrl)
88
140
  }
141
+
142
+ export default deploy
@@ -1,7 +1,7 @@
1
1
  import { HttpFunction } from '@google-cloud/functions-framework'
2
- import app from './build'
2
+ import { handler } from './build/handler.js'
3
3
 
4
- const svelteKitApp = HttpFunction(app.handler)
4
+ const svelteKitApp = new HttpFunction(handler)
5
5
 
6
6
  export {
7
7
  svelteKitApp
@@ -20,11 +20,9 @@ const getPublicURL: Adapter.getPublicURL = async ({ bucket, name }) => {
20
20
  return file.publicUrl()
21
21
  }
22
22
 
23
- const getSignedURL: Adapter.getSignedURL = async ({ bucket, name }) => {
23
+ const getSignedURL: Adapter.getSignedURL = async ({ bucket, name }, expires) => {
24
24
  const bucketInstance = getBucket(bucket)
25
25
  const file = bucketInstance.file(name)
26
- const expires = new Date()
27
- expires.setTime(expires.getTime() + 60 * 60 * 1_000)
28
26
  const [url] = await file.getSignedUrl({
29
27
  action: 'read',
30
28
  expires