@genoacms/adapter-gcp 0.5.2-fix.5 → 0.5.2-fix.8

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
+
@@ -1 +1,2 @@
1
- export default function (): Promise<void>;
1
+ declare function deploy(): Promise<void>;
2
+ export default deploy;
@@ -1,46 +1,17 @@
1
1
  import config from '../../config.js';
2
- import { getBucket } from '../storage/storage.js';
3
- import { readdir, lstat } from 'node:fs/promises';
4
2
  import { createReadStream, createWriteStream } from 'node:fs';
5
- import { join } from 'node:path';
6
- import { CloudFunctionsServiceClient } from '@google-cloud/functions';
3
+ import { resolve, dirname, basename } from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+ import { v2 } from '@google-cloud/functions';
7
6
  import archiver from 'archiver';
8
- const functionsClient = new CloudFunctionsServiceClient({
7
+ const { FunctionServiceClient } = v2;
8
+ const functionsClient = new FunctionServiceClient({
9
9
  credentials: config.deployment.credentials
10
10
  });
11
11
  const projectId = config.deployment.projectId;
12
12
  const region = config.deployment.region;
13
- 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
- });
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
- }
33
- async function uploadDirectory(bucketName, directoryPath, prefix = '') {
34
- const files = await readdir(directoryPath);
35
- const promises = [];
36
- for (const file of files) {
37
- const filePath = join(directoryPath, file);
38
- const destination = join(prefix, file);
39
- promises.push(uploadFileOrDirectory(bucketName, filePath, destination));
40
- }
41
- await Promise.all(promises);
42
- }
43
- async function zipDirectory(source, out) {
13
+ const currentDir = dirname(fileURLToPath(import.meta.url));
14
+ async function createZip(source, injectPaths, ignorePaths, out) {
44
15
  await new Promise((resolve, reject) => {
45
16
  const output = createWriteStream(out);
46
17
  const archive = archiver('zip', { zlib: { level: 9 } });
@@ -51,36 +22,90 @@ async function zipDirectory(source, out) {
51
22
  reject(err);
52
23
  });
53
24
  archive.pipe(output);
54
- archive.directory(source, false);
25
+ archive.glob(source, { ignore: ignorePaths });
26
+ for (const path of injectPaths) {
27
+ archive.file(path, { name: basename(path) });
28
+ }
55
29
  archive.finalize();
56
30
  });
57
31
  }
58
- async function deployFunction(functionName, source) {
32
+ async function uploadSource(sourceArchivePath) {
59
33
  const location = functionsClient.locationPath(projectId, region);
60
- const name = functionsClient.cloudFunctionPath(projectId, region, functionName);
61
- const [response] = await functionsClient.createFunction({
62
- location,
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,
63
65
  function: {
64
66
  name,
65
- sourceUploadUrl: source,
66
- entryPoint: 'handler',
67
- runtime: 'nodejs20',
68
- httpsTrigger: {},
69
- environmentVariables: {
70
- NODE_ENV: 'production'
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
+ }
71
81
  }
72
82
  }
73
- }, {});
83
+ };
84
+ let response;
85
+ if (isFunctionExisting) {
86
+ [response] = await functionsClient.updateFunction(operationParams);
87
+ }
88
+ else {
89
+ [response] = await functionsClient.createFunction(operationParams);
90
+ }
74
91
  console.log(response);
75
92
  }
76
- export default async function () {
77
- 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);
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);
86
110
  }
111
+ 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.8",
4
4
  "description": "Implementation of abstraction layer of GenoaCMS for GCP",
5
5
  "repository": {
6
6
  "type": "git",
@@ -37,11 +37,11 @@
37
37
  "vitest": "^1.0.4"
38
38
  },
39
39
  "peerDependencies": {
40
- "@google-cloud/functions-framework": "^3.3.0",
41
40
  "@sveltejs/adapter-node": "^4.0.1"
42
41
  },
43
42
  "files": [
44
43
  "src",
44
+ "deployment",
45
45
  "dist"
46
46
  ],
47
47
  "exports": {
@@ -1,52 +1,22 @@
1
1
  import config from '../../config.js'
2
- import { getBucket } from '../storage/storage.js'
3
- import { readdir, lstat } from 'node:fs/promises'
4
2
  import { createReadStream, createWriteStream } from 'node:fs'
5
- import { join } from 'node:path'
6
- import { CloudFunctionsServiceClient } from '@google-cloud/functions'
3
+ import { resolve, dirname, basename } from 'node:path'
4
+ import { fileURLToPath } from 'node:url'
5
+ import { v2 } from '@google-cloud/functions'
7
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
8
9
 
9
- const functionsClient = new CloudFunctionsServiceClient({
10
+ const { FunctionServiceClient } = v2
11
+ const functionsClient = new FunctionServiceClient({
10
12
  credentials: config.deployment.credentials
11
13
  })
12
14
  const projectId = config.deployment.projectId
13
15
  const region = config.deployment.region
14
16
 
15
- 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)
17
+ const currentDir = dirname(fileURLToPath(import.meta.url))
19
18
 
20
- await new Promise((resolve, reject) => {
21
- fileStream
22
- .pipe(gcsFile.createWriteStream())
23
- .on('error', reject)
24
- .on('finish', resolve)
25
- })
26
- }
27
-
28
- async function uploadFileOrDirectory (bucketName: string, path: string, prefix = ''): Promise<void> {
29
- const isDirectory = (await lstat(path)).isDirectory()
30
- if (isDirectory) {
31
- await uploadDirectory(bucketName, path, prefix)
32
- } else {
33
- await uploadFile(bucketName, path, prefix)
34
- }
35
- }
36
-
37
- async function uploadDirectory (bucketName: string, directoryPath: string, prefix = ''): Promise<void> {
38
- const files = await readdir(directoryPath)
39
- const promises = []
40
-
41
- for (const file of files) {
42
- const filePath = join(directoryPath, file)
43
- const destination = join(prefix, file)
44
- promises.push(uploadFileOrDirectory(bucketName, filePath, destination))
45
- }
46
- await Promise.all(promises)
47
- }
48
-
49
- async function zipDirectory (source: string, out: string): Promise<void> {
19
+ async function createZip (source: string, injectPaths: string[], ignorePaths: string[], out: string): Promise<void> {
50
20
  await new Promise<void>((resolve, reject) => {
51
21
  const output = createWriteStream(out)
52
22
  const archive = archiver('zip', { zlib: { level: 9 } })
@@ -60,38 +30,91 @@ async function zipDirectory (source: string, out: string): Promise<void> {
60
30
  })
61
31
 
62
32
  archive.pipe(output)
63
- archive.directory(source, false)
33
+ archive.glob(source, { ignore: ignorePaths })
34
+ for (const path of injectPaths) {
35
+ archive.file(path, { name: basename(path) })
36
+ }
64
37
  archive.finalize()
65
38
  })
66
39
  }
67
40
 
68
- async function deployFunction (functionName: string, source: string): Promise<void> {
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> {
69
61
  const location = functionsClient.locationPath(projectId, region)
70
- const name = functionsClient.cloudFunctionPath(projectId, region, functionName)
71
- const [response] = await functionsClient.createFunction({
72
- location,
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
73
  function: {
74
74
  name,
75
- sourceUploadUrl: source,
76
- entryPoint: 'handler',
77
- runtime: 'nodejs20',
78
- httpsTrigger: {},
79
- environmentVariables: {
80
- NODE_ENV: 'production'
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
+ }
81
89
  }
82
90
  }
83
- }, {})
91
+ }
92
+ let response
93
+ if (isFunctionExisting) {
94
+ [response] = await functionsClient.updateFunction(operationParams)
95
+ } else {
96
+ [response] = await functionsClient.createFunction(operationParams)
97
+ }
84
98
  console.log(response)
85
99
  }
86
100
 
87
- export default async function (): Promise<void> {
88
- 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)
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)
97
118
  }
119
+
120
+ export default deploy
@@ -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
@@ -1,8 +0,0 @@
1
- import { HttpFunction } from '@google-cloud/functions-framework'
2
- import app from './build'
3
-
4
- const svelteKitApp = HttpFunction(app.handler)
5
-
6
- export {
7
- svelteKitApp
8
- }