cloud-run-functions 0.1.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/LICENSE +21 -0
- package/config.schema.json +70 -0
- package/dist/chunk-XTHOFO7F.js +25 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +6 -0
- package/dist/main.d.ts +2 -0
- package/dist/main.js +29 -0
- package/dist/targets/dev.d.ts +2 -0
- package/dist/targets/dev.js +230 -0
- package/dist/tools/build.d.ts +2 -0
- package/dist/tools/build.js +0 -0
- package/dist/tools/dev.d.ts +8 -0
- package/dist/tools/dev.js +6 -0
- package/package.json +65 -0
- package/readme.md +29 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) Alec Larson
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
{
|
|
2
|
+
"type": "object",
|
|
3
|
+
"properties": {
|
|
4
|
+
"root": {
|
|
5
|
+
"oneOf": [
|
|
6
|
+
{
|
|
7
|
+
"description": "The base directory when searching for entry points.\n\nDefaults to the config directory (or if no config file is found, the current working directory).",
|
|
8
|
+
"type": "string"
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"type": "null"
|
|
12
|
+
}
|
|
13
|
+
]
|
|
14
|
+
},
|
|
15
|
+
"globs": {
|
|
16
|
+
"oneOf": [
|
|
17
|
+
{
|
|
18
|
+
"description": "The globs determine which directories are searched for entry points. Only \".ts\" and \".js\" files are matched, so you're not required to include them in the globs.\n\nBy default all directories in the \"root\" are searched.",
|
|
19
|
+
"type": "array",
|
|
20
|
+
"items": {
|
|
21
|
+
"type": "string"
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"type": "null"
|
|
26
|
+
}
|
|
27
|
+
]
|
|
28
|
+
},
|
|
29
|
+
"extensions": {
|
|
30
|
+
"oneOf": [
|
|
31
|
+
{
|
|
32
|
+
"description": "The extensions to match for entry points.\n\n@default [\".ts\", \".js\"]",
|
|
33
|
+
"type": "array",
|
|
34
|
+
"items": {
|
|
35
|
+
"type": "string"
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"type": "null"
|
|
40
|
+
}
|
|
41
|
+
]
|
|
42
|
+
},
|
|
43
|
+
"entrySuffix": {
|
|
44
|
+
"oneOf": [
|
|
45
|
+
{
|
|
46
|
+
"description": "The entry suffix should be a string like \".task\" or \".function\" which must be present in the file name (before the extension) or else that file will be ignored when scanning for entry points.\n\nIt can also be an empty string.",
|
|
47
|
+
"type": "string"
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"type": "null"
|
|
51
|
+
}
|
|
52
|
+
]
|
|
53
|
+
},
|
|
54
|
+
"adapter": {
|
|
55
|
+
"oneOf": [
|
|
56
|
+
{
|
|
57
|
+
"description": "The adapter wraps your Cloud Run functions at runtime, allowing you to write them with a platform-agnostic HTTP framework, like Hattip.\n\nSet this to \"node\" to skip using an adapter, in which case, your functions should conform to what @google-cloud/functions-framework expects.\n\n@default \"node\"",
|
|
58
|
+
"enum": [
|
|
59
|
+
"hattip",
|
|
60
|
+
"node"
|
|
61
|
+
]
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"type": "null"
|
|
65
|
+
}
|
|
66
|
+
]
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
"required": []
|
|
70
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// src/tools/dev.ts
|
|
2
|
+
import { findUpSync } from "find-up-simple";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import $ from "picospawn";
|
|
5
|
+
function dev(root) {
|
|
6
|
+
const packageDir = findUpSync("dist", {
|
|
7
|
+
cwd: import.meta.dirname,
|
|
8
|
+
type: "directory"
|
|
9
|
+
});
|
|
10
|
+
const source = path.join(packageDir, "targets/dev.js");
|
|
11
|
+
const binDir = path.resolve(packageDir, "../node_modules/.bin");
|
|
12
|
+
return $("functions-framework --target=dev --source", [source], {
|
|
13
|
+
stdio: "inherit",
|
|
14
|
+
env: {
|
|
15
|
+
...process.env,
|
|
16
|
+
CRF_ROOT: root,
|
|
17
|
+
CALLER_DIR: process.cwd(),
|
|
18
|
+
PATH: `${binDir}:${process.env.PATH}`
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export {
|
|
24
|
+
dev
|
|
25
|
+
};
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
package/dist/main.d.ts
ADDED
package/dist/main.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// src/main.ts
|
|
2
|
+
import * as ordana from "ordana";
|
|
3
|
+
var command = ordana.parse(process.argv.slice(2), {
|
|
4
|
+
name: "cloud-run-functions",
|
|
5
|
+
subcommands: {
|
|
6
|
+
dev: {
|
|
7
|
+
description: "Start the development server",
|
|
8
|
+
positionals: { maximum: 1 }
|
|
9
|
+
},
|
|
10
|
+
build: {
|
|
11
|
+
description: "Generate a bundle for each function",
|
|
12
|
+
positionals: { maximum: 1 }
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
|
+
if (command.type === "help") {
|
|
17
|
+
console.log(ordana.generateHelpMessage(command));
|
|
18
|
+
} else {
|
|
19
|
+
const subcommands = {
|
|
20
|
+
async dev() {
|
|
21
|
+
const { dev } = await import("./tools/dev.js");
|
|
22
|
+
await dev(command.positionals[0]);
|
|
23
|
+
},
|
|
24
|
+
async build() {
|
|
25
|
+
throw new Error("Not implemented");
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
await subcommands[command.subcommand]();
|
|
29
|
+
}
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
// src/targets/dev.ts
|
|
2
|
+
import functions from "@google-cloud/functions-framework";
|
|
3
|
+
import esbuild from "esbuild";
|
|
4
|
+
import { findUpSync } from "find-up-simple";
|
|
5
|
+
import os from "node:os";
|
|
6
|
+
import path2 from "node:path";
|
|
7
|
+
|
|
8
|
+
// src/config/index.ts
|
|
9
|
+
import * as z2 from "@zod/mini";
|
|
10
|
+
import Joycon from "joycon";
|
|
11
|
+
import path from "node:path";
|
|
12
|
+
|
|
13
|
+
// src/config/schema.ts
|
|
14
|
+
import * as z from "@zod/mini";
|
|
15
|
+
|
|
16
|
+
// node_modules/.pnpm/radashi@12.4.0/node_modules/radashi/dist/radashi.js
|
|
17
|
+
function dedent(text, ...values) {
|
|
18
|
+
var _a;
|
|
19
|
+
if (isArray(text)) {
|
|
20
|
+
if (values.length > 0) {
|
|
21
|
+
return dedent(
|
|
22
|
+
text.reduce((acc, input, i) => {
|
|
23
|
+
var _a2;
|
|
24
|
+
let value = String(values[i] ?? "");
|
|
25
|
+
const indent2 = value.includes("\n") && ((_a2 = input.match(/[ \t]*(?=[^\n]*$)/)) == null ? void 0 : _a2[0]);
|
|
26
|
+
if (indent2) {
|
|
27
|
+
value = value.replace(/\n(?=[^\n]*?\S)/g, "\n" + indent2);
|
|
28
|
+
}
|
|
29
|
+
return acc + input + value;
|
|
30
|
+
}, "")
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
text = text[0];
|
|
34
|
+
}
|
|
35
|
+
const indent = values[0] ?? ((_a = text.match(/^[ \t]*(?=\S)/m)) == null ? void 0 : _a[0]);
|
|
36
|
+
const output = indent ? text.replace(new RegExp(`^${indent}`, "gm"), "") : text;
|
|
37
|
+
return output.replace(/^[ \t]*\n|\n[ \t]*$/g, "");
|
|
38
|
+
}
|
|
39
|
+
var isArray = /* @__PURE__ */ (() => Array.isArray)();
|
|
40
|
+
var asyncIteratorSymbol = (
|
|
41
|
+
/* c8 ignore next */
|
|
42
|
+
Symbol.asyncIterator || Symbol.for("Symbol.asyncIterator")
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
// src/config/schema.ts
|
|
46
|
+
var configSchema = z.partial(
|
|
47
|
+
z.interface({
|
|
48
|
+
root: z.string().register(z.globalRegistry, {
|
|
49
|
+
description: dedent`
|
|
50
|
+
The base directory when searching for entry points.
|
|
51
|
+
|
|
52
|
+
Defaults to the config directory (or if no config file is found, the current working directory).
|
|
53
|
+
`
|
|
54
|
+
}),
|
|
55
|
+
globs: z.array(z.string()).register(z.globalRegistry, {
|
|
56
|
+
description: dedent`
|
|
57
|
+
The globs determine which directories are searched for entry points. Only ".ts" and ".js" files are matched, so you're not required to include them in the globs.
|
|
58
|
+
|
|
59
|
+
By default all directories in the "root" are searched.
|
|
60
|
+
`
|
|
61
|
+
}),
|
|
62
|
+
extensions: z.array(z.string()).register(z.globalRegistry, {
|
|
63
|
+
description: dedent`
|
|
64
|
+
The extensions to match for entry points.
|
|
65
|
+
|
|
66
|
+
@default [".ts", ".js"]
|
|
67
|
+
`
|
|
68
|
+
}),
|
|
69
|
+
entrySuffix: z.string().register(z.globalRegistry, {
|
|
70
|
+
description: dedent`
|
|
71
|
+
The entry suffix should be a string like ".task" or ".function" which must be present in the file name (before the extension) or else that file will be ignored when scanning for entry points.
|
|
72
|
+
|
|
73
|
+
It can also be an empty string.
|
|
74
|
+
`
|
|
75
|
+
}),
|
|
76
|
+
adapter: z.enum(["hattip", "node"]).register(z.globalRegistry, {
|
|
77
|
+
description: dedent`
|
|
78
|
+
The adapter wraps your Cloud Run functions at runtime, allowing you to write them with a platform-agnostic HTTP framework, like Hattip.
|
|
79
|
+
|
|
80
|
+
Set this to "node" to skip using an adapter, in which case, your functions should conform to what @google-cloud/functions-framework expects.
|
|
81
|
+
|
|
82
|
+
@default "node"
|
|
83
|
+
`
|
|
84
|
+
})
|
|
85
|
+
})
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
// src/config/index.ts
|
|
89
|
+
var joycon = new Joycon();
|
|
90
|
+
function loadConfig(cwd) {
|
|
91
|
+
const result = joycon.loadSync(["crf.config.json"], cwd);
|
|
92
|
+
if (!result.path) {
|
|
93
|
+
return {
|
|
94
|
+
configDir: null
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
return {
|
|
98
|
+
...z2.parse(configSchema, result.data),
|
|
99
|
+
configDir: path.dirname(result.path)
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// src/utils/emptyDir.ts
|
|
104
|
+
import fs from "node:fs";
|
|
105
|
+
function emptyDir(dir) {
|
|
106
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
107
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
108
|
+
return dir;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// src/utils/hash.ts
|
|
112
|
+
import crypto from "node:crypto";
|
|
113
|
+
function hash(data, len) {
|
|
114
|
+
return crypto.createHash("sha256").update(data).digest("hex").slice(0, len);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// src/targets/dev.ts
|
|
118
|
+
async function createBuild() {
|
|
119
|
+
const callerDir = process.env.CALLER_DIR ?? "";
|
|
120
|
+
const searchDir = process.env.CRF_ROOT ? path2.resolve(callerDir, process.env.CRF_ROOT) : callerDir;
|
|
121
|
+
const config = loadConfig(searchDir);
|
|
122
|
+
const root = config.configDir ? path2.resolve(config.configDir, config.root ?? "") : searchDir;
|
|
123
|
+
let pendingBuild;
|
|
124
|
+
const entryPoints = [];
|
|
125
|
+
const requiredSuffix = config.entrySuffix?.replace(/^\.?/, ".") ?? "";
|
|
126
|
+
const knownSuffixes = /* @__PURE__ */ new Set();
|
|
127
|
+
for (const glob of config.globs ?? ["**/*"]) {
|
|
128
|
+
let ext = path2.extname(glob);
|
|
129
|
+
if (ext) {
|
|
130
|
+
knownSuffixes.add(requiredSuffix + ext);
|
|
131
|
+
entryPoints.push(
|
|
132
|
+
requiredSuffix ? glob.replace(ext, requiredSuffix + ext) : glob
|
|
133
|
+
);
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
for (ext of config.extensions ?? [".ts", ".js"]) {
|
|
137
|
+
ext = requiredSuffix + ext;
|
|
138
|
+
knownSuffixes.add(ext);
|
|
139
|
+
entryPoints.push(glob + ext);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
const knownSuffixesRE = new RegExp(
|
|
143
|
+
`(${Array.from(knownSuffixes, (e) => e.replace(/\./g, "\\.")).sort((a, b) => b.length - a.length).join("|")})$`
|
|
144
|
+
);
|
|
145
|
+
const nodeModulesDir = findUpSync("node_modules", {
|
|
146
|
+
cwd: root,
|
|
147
|
+
type: "directory"
|
|
148
|
+
});
|
|
149
|
+
const outDir = emptyDir(
|
|
150
|
+
nodeModulesDir ? path2.join(nodeModulesDir, `.cache/cloud-run-functions-${hash(root, 8)}`) : path2.join(os.tmpdir(), `cloud-run-functions-${hash(root, 8)}`)
|
|
151
|
+
);
|
|
152
|
+
console.log({
|
|
153
|
+
root,
|
|
154
|
+
outDir,
|
|
155
|
+
entryPoints,
|
|
156
|
+
knownSuffixesRE
|
|
157
|
+
});
|
|
158
|
+
const context = await esbuild.context({
|
|
159
|
+
entryPoints,
|
|
160
|
+
absWorkingDir: root,
|
|
161
|
+
outdir: outDir,
|
|
162
|
+
bundle: true,
|
|
163
|
+
format: "esm",
|
|
164
|
+
packages: nodeModulesDir ? "external" : "bundle",
|
|
165
|
+
sourcemap: true,
|
|
166
|
+
metafile: true,
|
|
167
|
+
logOverride: {
|
|
168
|
+
"empty-glob": "silent"
|
|
169
|
+
},
|
|
170
|
+
plugins: [
|
|
171
|
+
{
|
|
172
|
+
name: "build-status",
|
|
173
|
+
setup(build) {
|
|
174
|
+
build.onStart(() => {
|
|
175
|
+
pendingBuild = Promise.withResolvers();
|
|
176
|
+
});
|
|
177
|
+
build.onEnd((result) => {
|
|
178
|
+
pendingBuild.resolve(result);
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
]
|
|
183
|
+
});
|
|
184
|
+
await context.watch();
|
|
185
|
+
console.log("[esbuild] Watching for changes...");
|
|
186
|
+
try {
|
|
187
|
+
const dotenv = await import("dotenv");
|
|
188
|
+
dotenv.config();
|
|
189
|
+
} catch {
|
|
190
|
+
}
|
|
191
|
+
return {
|
|
192
|
+
async match(url) {
|
|
193
|
+
const result = await pendingBuild.promise;
|
|
194
|
+
for (const [file, output] of Object.entries(
|
|
195
|
+
result.metafile?.outputs ?? {}
|
|
196
|
+
)) {
|
|
197
|
+
if (!output.entryPoint) {
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
const taskName = output.entryPoint.replace(knownSuffixesRE, "");
|
|
201
|
+
if (url.pathname === "/" + taskName) {
|
|
202
|
+
const taskPath = path2.join(root, file) + "?t=" + Date.now();
|
|
203
|
+
console.log("Importing:", taskPath);
|
|
204
|
+
const taskModule = await import(taskPath);
|
|
205
|
+
const taskHandler = taskModule.default;
|
|
206
|
+
switch (config.adapter) {
|
|
207
|
+
case "hattip": {
|
|
208
|
+
const { createMiddleware } = await import("@hattip/adapter-node");
|
|
209
|
+
return createMiddleware(taskHandler);
|
|
210
|
+
}
|
|
211
|
+
default:
|
|
212
|
+
return taskHandler;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
var buildPromise = createBuild();
|
|
221
|
+
functions.http("dev", async (req, res) => {
|
|
222
|
+
const url = new URL(req.url, "http://" + req.headers.host);
|
|
223
|
+
const build = await buildPromise;
|
|
224
|
+
const handler = await build.match(url);
|
|
225
|
+
if (handler) {
|
|
226
|
+
handler(req, res);
|
|
227
|
+
} else {
|
|
228
|
+
res.status(404).end();
|
|
229
|
+
}
|
|
230
|
+
});
|
|
File without changes
|
package/package.json
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "cloud-run-functions",
|
|
3
|
+
"type": "module",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"bin": "./dist/main.js",
|
|
6
|
+
"exports": {
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"default": "./dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist",
|
|
12
|
+
"config.schema.json"
|
|
13
|
+
],
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"author": "Alec Larson",
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "https://github.com/alloc/cloud-run-functions.git"
|
|
19
|
+
},
|
|
20
|
+
"prettier": "@alloc/prettier-config",
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@alloc/prettier-config": "^1.0.0",
|
|
23
|
+
"@google-cloud/functions-framework": "latest",
|
|
24
|
+
"@hattip/adapter-node": "latest",
|
|
25
|
+
"@hattip/core": "latest",
|
|
26
|
+
"@types/node": "^22.15.17",
|
|
27
|
+
"dotenv": "^16.5.0",
|
|
28
|
+
"prettier": "^3.5.3",
|
|
29
|
+
"radashi": "^12.4.0",
|
|
30
|
+
"rimraf": "^6.0.1",
|
|
31
|
+
"tsc-lint": "^0.1.9",
|
|
32
|
+
"tsup": "^8.4.0",
|
|
33
|
+
"typescript": "^5.8.3",
|
|
34
|
+
"vitest": "^3.1.3"
|
|
35
|
+
},
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"@zod/mini": "4.0.0-beta.0",
|
|
38
|
+
"esbuild": "^0.25.4",
|
|
39
|
+
"find-up-simple": "^1.0.1",
|
|
40
|
+
"joycon": "^3.1.1",
|
|
41
|
+
"ordana": "^0.4.0",
|
|
42
|
+
"picospawn": "^0.3.1",
|
|
43
|
+
"tinyglobby": "^0.2.13"
|
|
44
|
+
},
|
|
45
|
+
"peerDependencies": {
|
|
46
|
+
"@google-cloud/functions-framework": "*",
|
|
47
|
+
"@hattip/adapter-node": "*",
|
|
48
|
+
"dotenv": "*"
|
|
49
|
+
},
|
|
50
|
+
"peerDependenciesMeta": {
|
|
51
|
+
"@hattip/adapter-node": {
|
|
52
|
+
"optional": true
|
|
53
|
+
},
|
|
54
|
+
"dotenv": {
|
|
55
|
+
"optional": true
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
"scripts": {
|
|
59
|
+
"dev": "rimraf dist && tsup --sourcemap --watch",
|
|
60
|
+
"build": "rimraf dist && tsup",
|
|
61
|
+
"format": "prettier --write .",
|
|
62
|
+
"lint": "tsc-lint",
|
|
63
|
+
"test": "vitest"
|
|
64
|
+
}
|
|
65
|
+
}
|
package/readme.md
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# cloud-run-functions
|
|
2
|
+
|
|
3
|
+
> ⚠️ This project is not ready for production use.
|
|
4
|
+
|
|
5
|
+
Enhances the `@google-cloud/functions-framework` package with "hot reloading" (for local development) and a bundler (for deployment).
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
pnpm add cloud-run-functions -D
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Development server
|
|
12
|
+
|
|
13
|
+
The dev server uses the `@google-cloud/functions-framework` package to run your functions locally. We handle _transpiling_ and _hot reloading_ for you. Just tell the dev server where to find your functions.
|
|
14
|
+
|
|
15
|
+
```sh
|
|
16
|
+
npx cloud-run-functions dev ./path/to/functions/
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
By default, any `.ts` or `.js` module is considered "loadable" by the dev server. This behavior is configurable with a `crf.config.json` file.
|
|
20
|
+
|
|
21
|
+
The dev server uses filesystem routing. By default, the dev server runs on port 8080. So if you do `http get :8080/hello` from your terminal, the dev server will look for a file called `hello.ts` or `hello.js` in the `./path/to/functions/` directory. If that file exists, its default export will be used as the function handler.
|
|
22
|
+
|
|
23
|
+
## Bundling
|
|
24
|
+
|
|
25
|
+
When you're ready to deploy, use the `build` command to bundle your functions.
|
|
26
|
+
|
|
27
|
+
```sh
|
|
28
|
+
npx cloud-run-functions build ./path/to/functions/
|
|
29
|
+
```
|