@genoacms/adapter-gcp 0.5.2-fix.5 → 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,16 +10,16 @@ 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
+ }
13
19
  async function uploadFile(bucketName, filePath, destination) {
14
- const bucket = getBucket(bucketName);
15
- const fileStream = createReadStream(filePath);
16
- const gcsFile = bucket.file(destination);
17
- await new Promise((resolve, reject) => {
18
- fileStream
19
- .pipe(gcsFile.createWriteStream())
20
- .on('error', reject)
21
- .on('finish', resolve);
22
- });
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
23
  }
24
24
  async function uploadFileOrDirectory(bucketName, path, prefix = '') {
25
25
  const isDirectory = (await lstat(path)).isDirectory();
@@ -40,7 +40,7 @@ async function uploadDirectory(bucketName, directoryPath, prefix = '') {
40
40
  }
41
41
  await Promise.all(promises);
42
42
  }
43
- async function zipDirectory(source, out) {
43
+ async function createZip(source, injectPaths, ignorePaths, out) {
44
44
  await new Promise((resolve, reject) => {
45
45
  const output = createWriteStream(out);
46
46
  const archive = archiver('zip', { zlib: { level: 9 } });
@@ -51,36 +51,83 @@ async function zipDirectory(source, out) {
51
51
  reject(err);
52
52
  });
53
53
  archive.pipe(output);
54
- archive.directory(source, false);
54
+ archive.glob(source, { ignore: ignorePaths });
55
+ for (const path of injectPaths) {
56
+ archive.append(path, { name: path });
57
+ }
55
58
  archive.finalize();
56
59
  });
57
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
+ }
58
79
  async function deployFunction(functionName, source) {
59
80
  const location = functionsClient.locationPath(projectId, region);
60
81
  const name = functionsClient.cloudFunctionPath(projectId, region, functionName);
61
- 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 = {
62
91
  location,
63
92
  function: {
64
93
  name,
65
94
  sourceUploadUrl: source,
66
- entryPoint: 'handler',
95
+ entryPoint: 'svelteKitApp',
67
96
  runtime: 'nodejs20',
68
97
  httpsTrigger: {},
69
98
  environmentVariables: {
70
99
  NODE_ENV: 'production'
71
100
  }
72
101
  }
73
- }, {});
74
- 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);
75
111
  }
76
- export default async function () {
112
+ async function deploy() {
77
113
  const bucketName = config.storage.defaultBucket;
78
- const assetsPath = '.genoacms/deployment/static';
79
- const buildArchiveSrc = '.build.zip';
80
- const buildArchiveDest = '.genoacms/deployment/build.zip';
81
- const buildArchiveRef = `gs://${bucketName}/${buildArchiveDest}`;
82
- await zipDirectory('./build', buildArchiveSrc);
83
- await uploadDirectory(bucketName, './static', assetsPath);
84
- await uploadFile(bucketName, buildArchiveSrc, buildArchiveDest);
85
- 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);
86
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.5",
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,18 +11,17 @@ 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
14
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
+ }
15
21
  async function uploadFile (bucketName: string, filePath: string, destination: string): Promise<void> {
16
- const bucket = getBucket(bucketName)
17
- const fileStream = createReadStream(filePath)
18
- const gcsFile = bucket.file(destination)
19
-
20
- await new Promise((resolve, reject) => {
21
- fileStream
22
- .pipe(gcsFile.createWriteStream())
23
- .on('error', reject)
24
- .on('finish', resolve)
25
- })
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))
26
25
  }
27
26
 
28
27
  async function uploadFileOrDirectory (bucketName: string, path: string, prefix = ''): Promise<void> {
@@ -46,7 +45,7 @@ async function uploadDirectory (bucketName: string, directoryPath: string, prefi
46
45
  await Promise.all(promises)
47
46
  }
48
47
 
49
- async function zipDirectory (source: string, out: string): Promise<void> {
48
+ async function createZip (source: string, injectPaths: string[], ignorePaths: string[], out: string): Promise<void> {
50
49
  await new Promise<void>((resolve, reject) => {
51
50
  const output = createWriteStream(out)
52
51
  const archive = archiver('zip', { zlib: { level: 9 } })
@@ -60,38 +59,84 @@ async function zipDirectory (source: string, out: string): Promise<void> {
60
59
  })
61
60
 
62
61
  archive.pipe(output)
63
- archive.directory(source, false)
62
+ archive.glob(source, { ignore: ignorePaths })
63
+ for (const path of injectPaths) {
64
+ archive.append(path, { name: path })
65
+ }
64
66
  archive.finalize()
65
67
  })
66
68
  }
67
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
+
68
88
  async function deployFunction (functionName: string, source: string): Promise<void> {
69
89
  const location = functionsClient.locationPath(projectId, region)
70
90
  const name = functionsClient.cloudFunctionPath(projectId, region, functionName)
71
- 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 = {
72
99
  location,
73
100
  function: {
74
101
  name,
75
102
  sourceUploadUrl: source,
76
- entryPoint: 'handler',
103
+ entryPoint: 'svelteKitApp',
77
104
  runtime: 'nodejs20',
78
105
  httpsTrigger: {},
79
106
  environmentVariables: {
80
107
  NODE_ENV: 'production'
81
108
  }
82
109
  }
83
- }, {})
84
- 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)
85
118
  }
86
119
 
87
- export default async function (): Promise<void> {
120
+ async function deploy (): Promise<void> {
88
121
  const bucketName = config.storage.defaultBucket
89
- const assetsPath = '.genoacms/deployment/static'
90
- const buildArchiveSrc = '.build.zip'
91
- const buildArchiveDest = '.genoacms/deployment/build.zip'
92
- const buildArchiveRef = `gs://${bucketName}/${buildArchiveDest}`
93
- await zipDirectory('./build', buildArchiveSrc)
94
- await uploadDirectory(bucketName, './static', assetsPath)
95
- await uploadFile(bucketName, buildArchiveSrc, buildArchiveDest)
96
- 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)
97
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