@yc-tools/functions-yc 1.0.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/dist/build/index.d.ts +50 -0
- package/dist/build/index.d.ts.map +1 -0
- package/dist/build/index.js +176 -0
- package/dist/build/index.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +209 -0
- package/dist/index.js.map +1 -0
- package/dist/terraform/index.d.ts +47 -0
- package/dist/terraform/index.d.ts.map +1 -0
- package/dist/terraform/index.js +137 -0
- package/dist/terraform/index.js.map +1 -0
- package/dist/terraform/project/backend.tf +3 -0
- package/dist/terraform/project/main.tf +183 -0
- package/dist/terraform/project/outputs.tf +29 -0
- package/dist/terraform/project/providers.tf +8 -0
- package/dist/terraform/project/variables.tf +120 -0
- package/dist/terraform/project/versions.tf +10 -0
- package/dist/upload/index.d.ts +16 -0
- package/dist/upload/index.d.ts.map +1 -0
- package/dist/upload/index.js +45 -0
- package/dist/upload/index.js.map +1 -0
- package/package.json +59 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export interface FunctionEntry {
|
|
2
|
+
name: string;
|
|
3
|
+
zipPath: string;
|
|
4
|
+
entry: string;
|
|
5
|
+
route: string;
|
|
6
|
+
params: string[];
|
|
7
|
+
memory: number;
|
|
8
|
+
timeout: number;
|
|
9
|
+
env: Record<string, string>;
|
|
10
|
+
}
|
|
11
|
+
export interface FunctionsManifest {
|
|
12
|
+
appName: string;
|
|
13
|
+
buildId: string;
|
|
14
|
+
timestamp: string;
|
|
15
|
+
functions: FunctionEntry[];
|
|
16
|
+
}
|
|
17
|
+
export interface BuildOptions {
|
|
18
|
+
projectPath: string;
|
|
19
|
+
outputDir: string;
|
|
20
|
+
handlersDir?: string;
|
|
21
|
+
appName?: string;
|
|
22
|
+
buildId?: string;
|
|
23
|
+
externalPackages?: string[];
|
|
24
|
+
memory?: number;
|
|
25
|
+
timeout?: number;
|
|
26
|
+
verbose?: boolean;
|
|
27
|
+
}
|
|
28
|
+
export declare class Builder {
|
|
29
|
+
build(options: BuildOptions): Promise<FunctionsManifest>;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Convert a file path (relative to handlers/) to an API route.
|
|
33
|
+
*
|
|
34
|
+
* tg/[botId]/index.ts → /tg/{botId}
|
|
35
|
+
* webhook.ts → /webhook
|
|
36
|
+
* index.ts → /
|
|
37
|
+
*/
|
|
38
|
+
export declare function filePathToRoute(filePath: string): string;
|
|
39
|
+
/**
|
|
40
|
+
* Extract path parameter names from a route.
|
|
41
|
+
* /tg/{botId} → ['botId']
|
|
42
|
+
*/
|
|
43
|
+
export declare function extractRouteParams(route: string): string[];
|
|
44
|
+
/**
|
|
45
|
+
* Convert a route to a valid terraform resource name.
|
|
46
|
+
* /tg/{botId} → tg-botId
|
|
47
|
+
* / → root
|
|
48
|
+
*/
|
|
49
|
+
export declare function routeToFunctionName(route: string): string;
|
|
50
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/build/index.ts"],"names":[],"mappings":"AAQA,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC7B;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,aAAa,EAAE,CAAC;CAC5B;AAED,MAAM,WAAW,YAAY;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,qBAAa,OAAO;IACZ,KAAK,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,iBAAiB,CAAC;CA2G/D;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAQxD;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAQ1D;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAQzD"}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import fs from 'fs-extra';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import archiver from 'archiver';
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
import ora from 'ora';
|
|
6
|
+
import * as esbuild from 'esbuild';
|
|
7
|
+
import { glob } from 'glob';
|
|
8
|
+
export class Builder {
|
|
9
|
+
async build(options) {
|
|
10
|
+
const { projectPath, outputDir, handlersDir = 'handlers', externalPackages = [], memory = 256, timeout = 30, verbose, } = options;
|
|
11
|
+
const spinner = ora();
|
|
12
|
+
const artifactsDir = path.join(outputDir, 'artifacts');
|
|
13
|
+
await fs.ensureDir(artifactsDir);
|
|
14
|
+
const buildId = options.buildId ?? generateBuildId();
|
|
15
|
+
const appName = options.appName ?? path.basename(projectPath);
|
|
16
|
+
// Scan handlers
|
|
17
|
+
const handlersAbsDir = path.resolve(projectPath, handlersDir);
|
|
18
|
+
if (!(await fs.pathExists(handlersAbsDir))) {
|
|
19
|
+
throw new Error(`Handlers directory not found: ${handlersAbsDir}`);
|
|
20
|
+
}
|
|
21
|
+
spinner.start('Scanning handlers...');
|
|
22
|
+
const handlerFiles = await glob('**/*.ts', { cwd: handlersAbsDir, absolute: false });
|
|
23
|
+
if (handlerFiles.length === 0) {
|
|
24
|
+
throw new Error(`No .ts files found in: ${handlersAbsDir}`);
|
|
25
|
+
}
|
|
26
|
+
spinner.succeed(`Found ${handlerFiles.length} handler(s)`);
|
|
27
|
+
// Build each handler
|
|
28
|
+
const functions = [];
|
|
29
|
+
const tempDir = path.join(outputDir, '.tmp-build');
|
|
30
|
+
await fs.ensureDir(tempDir);
|
|
31
|
+
try {
|
|
32
|
+
for (const relFile of handlerFiles.sort()) {
|
|
33
|
+
const route = filePathToRoute(relFile);
|
|
34
|
+
const params = extractRouteParams(route);
|
|
35
|
+
const name = routeToFunctionName(route);
|
|
36
|
+
if (verbose) {
|
|
37
|
+
console.log(chalk.gray(` ${relFile} → ${route}`));
|
|
38
|
+
}
|
|
39
|
+
const entryAbsolute = path.join(handlersAbsDir, relFile);
|
|
40
|
+
const wrapperPath = path.join(tempDir, `${name}-entry.cjs`);
|
|
41
|
+
const distPath = path.join(tempDir, `${name}-bundle.cjs`);
|
|
42
|
+
const zipPath = path.join(artifactsDir, `${name}.zip`);
|
|
43
|
+
await fs.writeFile(wrapperPath, generateWrapper(entryAbsolute));
|
|
44
|
+
spinner.start(`Bundling ${name}...`);
|
|
45
|
+
await esbuild.build({
|
|
46
|
+
entryPoints: [wrapperPath],
|
|
47
|
+
bundle: true,
|
|
48
|
+
platform: 'node',
|
|
49
|
+
target: 'node20',
|
|
50
|
+
format: 'cjs',
|
|
51
|
+
outfile: distPath,
|
|
52
|
+
minify: true,
|
|
53
|
+
treeShaking: true,
|
|
54
|
+
logLevel: 'warning',
|
|
55
|
+
external: externalPackages,
|
|
56
|
+
});
|
|
57
|
+
if (externalPackages.length > 0) {
|
|
58
|
+
await zipBundleWithNodeModules(distPath, projectPath, externalPackages, zipPath);
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
await zipFile(distPath, zipPath, 'index.js');
|
|
62
|
+
}
|
|
63
|
+
functions.push({
|
|
64
|
+
name,
|
|
65
|
+
zipPath: path.relative(outputDir, zipPath),
|
|
66
|
+
entry: 'index.handler',
|
|
67
|
+
route,
|
|
68
|
+
params,
|
|
69
|
+
memory,
|
|
70
|
+
timeout,
|
|
71
|
+
env: { NODE_ENV: 'production' },
|
|
72
|
+
});
|
|
73
|
+
spinner.succeed(`Built ${name} → ${route}`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
finally {
|
|
77
|
+
await fs.remove(tempDir);
|
|
78
|
+
}
|
|
79
|
+
const manifest = {
|
|
80
|
+
appName,
|
|
81
|
+
buildId,
|
|
82
|
+
timestamp: new Date().toISOString(),
|
|
83
|
+
functions,
|
|
84
|
+
};
|
|
85
|
+
const manifestPath = path.join(outputDir, 'functions.manifest.json');
|
|
86
|
+
await fs.writeJson(manifestPath, manifest, { spaces: 2 });
|
|
87
|
+
if (verbose) {
|
|
88
|
+
console.log(chalk.gray(` Manifest: ${manifestPath}`));
|
|
89
|
+
}
|
|
90
|
+
return manifest;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Convert a file path (relative to handlers/) to an API route.
|
|
95
|
+
*
|
|
96
|
+
* tg/[botId]/index.ts → /tg/{botId}
|
|
97
|
+
* webhook.ts → /webhook
|
|
98
|
+
* index.ts → /
|
|
99
|
+
*/
|
|
100
|
+
export function filePathToRoute(filePath) {
|
|
101
|
+
let p = filePath.replace(/\\/g, '/');
|
|
102
|
+
p = p.replace(/\.ts$/, '');
|
|
103
|
+
p = p.replace(/\/index$/, '');
|
|
104
|
+
if (p === '' || p === 'index')
|
|
105
|
+
return '/';
|
|
106
|
+
p = p.replace(/\[([^\]]+)\]/g, '{$1}');
|
|
107
|
+
if (!p.startsWith('/'))
|
|
108
|
+
p = '/' + p;
|
|
109
|
+
return p;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Extract path parameter names from a route.
|
|
113
|
+
* /tg/{botId} → ['botId']
|
|
114
|
+
*/
|
|
115
|
+
export function extractRouteParams(route) {
|
|
116
|
+
const params = [];
|
|
117
|
+
const re = /\{([^}]+)\}/g;
|
|
118
|
+
let m;
|
|
119
|
+
while ((m = re.exec(route)) !== null) {
|
|
120
|
+
params.push(m[1]);
|
|
121
|
+
}
|
|
122
|
+
return params;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Convert a route to a valid terraform resource name.
|
|
126
|
+
* /tg/{botId} → tg-botId
|
|
127
|
+
* / → root
|
|
128
|
+
*/
|
|
129
|
+
export function routeToFunctionName(route) {
|
|
130
|
+
return (route
|
|
131
|
+
.replace(/^\//, '')
|
|
132
|
+
.replace(/\{([^}]+)\}/g, '$1')
|
|
133
|
+
.replace(/\//g, '-')
|
|
134
|
+
.replace(/[^a-zA-Z0-9-]/g, '') || 'root');
|
|
135
|
+
}
|
|
136
|
+
function generateWrapper(entryAbsolutePath) {
|
|
137
|
+
const escaped = JSON.stringify(entryAbsolutePath);
|
|
138
|
+
return `'use strict';
|
|
139
|
+
const _mod = require(${escaped});
|
|
140
|
+
const _handler = _mod.handler || _mod.default?.handler;
|
|
141
|
+
exports.handler = async (event, context) => {
|
|
142
|
+
event.params = event.pathParameters || {};
|
|
143
|
+
return _handler(event, context);
|
|
144
|
+
};
|
|
145
|
+
`;
|
|
146
|
+
}
|
|
147
|
+
function generateBuildId() {
|
|
148
|
+
return new Date().toISOString().replace(/[^0-9]/g, '').slice(0, 14);
|
|
149
|
+
}
|
|
150
|
+
async function zipFile(sourcePath, destZip, entryName) {
|
|
151
|
+
await new Promise((resolve, reject) => {
|
|
152
|
+
const output = fs.createWriteStream(destZip);
|
|
153
|
+
const archive = archiver('zip', { zlib: { level: 9 } });
|
|
154
|
+
output.on('close', resolve);
|
|
155
|
+
archive.on('error', reject);
|
|
156
|
+
archive.pipe(output);
|
|
157
|
+
archive.file(sourcePath, { name: entryName });
|
|
158
|
+
void archive.finalize();
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
async function zipBundleWithNodeModules(bundlePath, projectPath, externals, destZip) {
|
|
162
|
+
await new Promise((resolve, reject) => {
|
|
163
|
+
const output = fs.createWriteStream(destZip);
|
|
164
|
+
const archive = archiver('zip', { zlib: { level: 9 } });
|
|
165
|
+
output.on('close', resolve);
|
|
166
|
+
archive.on('error', reject);
|
|
167
|
+
archive.pipe(output);
|
|
168
|
+
archive.file(bundlePath, { name: 'index.js' });
|
|
169
|
+
for (const pkg of externals) {
|
|
170
|
+
const pkgDir = path.join(projectPath, 'node_modules', pkg);
|
|
171
|
+
archive.directory(pkgDir, `node_modules/${pkg}`);
|
|
172
|
+
}
|
|
173
|
+
void archive.finalize();
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/build/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import fs from 'fs-extra';
|
|
6
|
+
import { Builder } from './build/index.js';
|
|
7
|
+
import { Uploader } from './upload/index.js';
|
|
8
|
+
import { cleanupTerraformProject, extractOutputString, prepareTerraformProject, resolveBackendConfig, TerraformRunner, } from './terraform/index.js';
|
|
9
|
+
const program = new Command();
|
|
10
|
+
program
|
|
11
|
+
.name('functions-yc')
|
|
12
|
+
.description('Deploy TypeScript functions to Yandex Cloud')
|
|
13
|
+
.version('1.0.0');
|
|
14
|
+
async function loadConfig(projectPath) {
|
|
15
|
+
const configPath = path.join(projectPath, 'functions-yc.config.json');
|
|
16
|
+
if (await fs.pathExists(configPath)) {
|
|
17
|
+
return (await fs.readJson(configPath));
|
|
18
|
+
}
|
|
19
|
+
return {};
|
|
20
|
+
}
|
|
21
|
+
function e(key) {
|
|
22
|
+
return process.env[key] || undefined;
|
|
23
|
+
}
|
|
24
|
+
function first(...values) {
|
|
25
|
+
return values.find((v) => v !== undefined);
|
|
26
|
+
}
|
|
27
|
+
function collectExternalPackages(cliValues, config) {
|
|
28
|
+
if (cliValues.length > 0)
|
|
29
|
+
return cliValues;
|
|
30
|
+
if (Array.isArray(config.externalPackages))
|
|
31
|
+
return config.externalPackages;
|
|
32
|
+
return [];
|
|
33
|
+
}
|
|
34
|
+
// ── build ─────────────────────────────────────────────────────────────────────
|
|
35
|
+
program
|
|
36
|
+
.command('build')
|
|
37
|
+
.description('Scan handlers/, bundle with esbuild, write functions.manifest.json')
|
|
38
|
+
.option('-p, --project <path>', 'Project root', '.')
|
|
39
|
+
.option('-o, --output <path>', 'Output directory', '.fyc-out')
|
|
40
|
+
.option('--handlers-dir <dir>', 'Handlers directory (relative to project)', '')
|
|
41
|
+
.option('--app-name <name>', 'Application name')
|
|
42
|
+
.option('--external <pkg>', 'Mark package external and copy from node_modules (repeatable)', (v, acc) => { acc.push(v); return acc; }, [])
|
|
43
|
+
.option('--memory <mb>', 'Function memory in MB', '')
|
|
44
|
+
.option('--timeout <s>', 'Function timeout in seconds', '')
|
|
45
|
+
.option('-v, --verbose', 'Verbose output')
|
|
46
|
+
.action(async (opts) => {
|
|
47
|
+
try {
|
|
48
|
+
const projectPath = path.resolve(opts.project);
|
|
49
|
+
const outputDir = path.resolve(opts.output);
|
|
50
|
+
const config = await loadConfig(projectPath);
|
|
51
|
+
const builder = new Builder();
|
|
52
|
+
const manifest = await builder.build({
|
|
53
|
+
projectPath,
|
|
54
|
+
outputDir,
|
|
55
|
+
handlersDir: opts.handlersDir || config.handlersDir || 'handlers',
|
|
56
|
+
appName: opts.appName || config.appName,
|
|
57
|
+
externalPackages: collectExternalPackages(opts.external, config),
|
|
58
|
+
memory: opts.memory ? parseInt(opts.memory, 10) : config.memory,
|
|
59
|
+
timeout: opts.timeout ? parseInt(opts.timeout, 10) : config.timeout,
|
|
60
|
+
verbose: opts.verbose,
|
|
61
|
+
});
|
|
62
|
+
console.log(chalk.green(`\nBuild complete: ${manifest.functions.length} function(s)`));
|
|
63
|
+
for (const fn of manifest.functions) {
|
|
64
|
+
console.log(chalk.gray(` ${fn.name}: ${fn.route}`));
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
console.error(chalk.red('Build failed:'), error instanceof Error ? error.message : String(error));
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
// ── deploy ────────────────────────────────────────────────────────────────────
|
|
73
|
+
program
|
|
74
|
+
.command('deploy')
|
|
75
|
+
.description('Build, upload artifacts, and run terraform apply')
|
|
76
|
+
.option('-p, --project <path>', 'Project root', '.')
|
|
77
|
+
.option('-o, --output <path>', 'Output directory', '.fyc-out')
|
|
78
|
+
.option('--handlers-dir <dir>', 'Handlers directory (relative to project)', '')
|
|
79
|
+
.option('--app-name <name>', 'Application name')
|
|
80
|
+
.option('--external <pkg>', 'Mark package external and copy from node_modules (repeatable)', (v, acc) => { acc.push(v); return acc; }, [])
|
|
81
|
+
.option('--memory <mb>', 'Function memory in MB', '')
|
|
82
|
+
.option('--timeout <s>', 'Function timeout in seconds', '')
|
|
83
|
+
.option('--cloud-id <id>', 'Yandex Cloud ID')
|
|
84
|
+
.option('--folder-id <id>', 'Yandex Cloud Folder ID')
|
|
85
|
+
.option('--iam-token <token>', 'Yandex Cloud IAM token')
|
|
86
|
+
.option('--access-key <key>', 'Object Storage access key for upload')
|
|
87
|
+
.option('--secret-key <key>', 'Object Storage secret key for upload')
|
|
88
|
+
.option('--bucket <name>', 'Deploy bucket name')
|
|
89
|
+
.option('--state-bucket <name>', 'Terraform S3 state bucket')
|
|
90
|
+
.option('--state-key <key>', 'Terraform S3 state key')
|
|
91
|
+
.option('--nodejs-version <ver>', 'Node.js version (nodejs18/nodejs20/nodejs22)', '')
|
|
92
|
+
.option('--domain <name>', 'Custom domain name')
|
|
93
|
+
.option('--env <env>', 'Environment (dev/staging/production)', '')
|
|
94
|
+
.option('--auto-approve', 'Run terraform with -auto-approve')
|
|
95
|
+
.option('-v, --verbose', 'Verbose output')
|
|
96
|
+
.action(async (opts) => {
|
|
97
|
+
const projectPath = path.resolve(opts.project);
|
|
98
|
+
const outputDir = path.resolve(opts.output);
|
|
99
|
+
const config = await loadConfig(projectPath);
|
|
100
|
+
const cloudId = first(opts.cloudId, e('FYC_CLOUD_ID'), config.cloudId);
|
|
101
|
+
const folderId = first(opts.folderId, e('FYC_FOLDER_ID'), config.folderId);
|
|
102
|
+
const iamToken = first(opts.iamToken, e('FYC_IAM_TOKEN'), config.iamToken);
|
|
103
|
+
const accessKey = first(opts.accessKey, e('FYC_STORAGE_ACCESS_KEY'), config.storageAccessKey);
|
|
104
|
+
const secretKey = first(opts.secretKey, e('FYC_STORAGE_SECRET_KEY'), config.storageSecretKey);
|
|
105
|
+
const stateBucket = first(opts.stateBucket, e('FYC_STATE_BUCKET'), config.stateBucket);
|
|
106
|
+
const stateKey = first(opts.stateKey, e('FYC_STATE_KEY'), config.stateKey);
|
|
107
|
+
const appName = first(opts.appName, e('FYC_APP_NAME'), config.appName);
|
|
108
|
+
const nodejsVersion = first(opts.nodejsVersion ? opts.nodejsVersion : undefined, config.nodejsVersion, 'nodejs20');
|
|
109
|
+
const environment = first(opts.env ? opts.env : undefined, config.env, 'production');
|
|
110
|
+
const autoApprove = opts.autoApprove ||
|
|
111
|
+
e('FYC_AUTO_APPROVE') === 'true' ||
|
|
112
|
+
config.autoApprove ||
|
|
113
|
+
false;
|
|
114
|
+
const domainName = first(opts.domain, config.domainName);
|
|
115
|
+
const terraformDir = await prepareTerraformProject();
|
|
116
|
+
try {
|
|
117
|
+
// 1. Build
|
|
118
|
+
const builder = new Builder();
|
|
119
|
+
const manifest = await builder.build({
|
|
120
|
+
projectPath,
|
|
121
|
+
outputDir,
|
|
122
|
+
handlersDir: opts.handlersDir || config.handlersDir || 'handlers',
|
|
123
|
+
appName,
|
|
124
|
+
externalPackages: collectExternalPackages(opts.external, config),
|
|
125
|
+
memory: opts.memory ? parseInt(opts.memory, 10) : config.memory,
|
|
126
|
+
timeout: opts.timeout ? parseInt(opts.timeout, 10) : config.timeout,
|
|
127
|
+
verbose: opts.verbose,
|
|
128
|
+
});
|
|
129
|
+
// 2. Terraform init
|
|
130
|
+
const terraform = new TerraformRunner(terraformDir);
|
|
131
|
+
const backend = resolveBackendConfig({ stateBucket, stateKey }, {
|
|
132
|
+
...process.env,
|
|
133
|
+
YC_REGION: 'ru-central1',
|
|
134
|
+
YC_ACCESS_KEY: accessKey,
|
|
135
|
+
YC_SECRET_KEY: secretKey,
|
|
136
|
+
});
|
|
137
|
+
await terraform.init(backend || undefined);
|
|
138
|
+
// 3. Upload
|
|
139
|
+
const resolvedAccessKey = accessKey ?? '';
|
|
140
|
+
const resolvedSecretKey = secretKey ?? '';
|
|
141
|
+
if (!resolvedAccessKey || !resolvedSecretKey) {
|
|
142
|
+
throw new Error('Object Storage credentials required for upload. Provide --access-key/--secret-key or FYC_STORAGE_ACCESS_KEY/FYC_STORAGE_SECRET_KEY.');
|
|
143
|
+
}
|
|
144
|
+
// Try to get existing bucket from terraform state, fall back to CLI/config
|
|
145
|
+
let deployBucket = first(opts.bucket, config.deployBucketName);
|
|
146
|
+
if (!deployBucket) {
|
|
147
|
+
try {
|
|
148
|
+
const outputs = await terraform.readOutputs();
|
|
149
|
+
deployBucket = extractOutputString(outputs, 'deploy_bucket');
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
// no state yet — bucket will be created by terraform
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
const uploader = new Uploader();
|
|
156
|
+
await uploader.upload({
|
|
157
|
+
outputDir,
|
|
158
|
+
manifest,
|
|
159
|
+
bucket: deployBucket ?? `${(appName ?? path.basename(projectPath)).toLowerCase()}-${environment}-deploy`,
|
|
160
|
+
accessKey: resolvedAccessKey,
|
|
161
|
+
secretKey: resolvedSecretKey,
|
|
162
|
+
verbose: opts.verbose,
|
|
163
|
+
});
|
|
164
|
+
// 4. Terraform apply
|
|
165
|
+
const tfVarEnv = {
|
|
166
|
+
...process.env,
|
|
167
|
+
TF_VAR_manifest_path: path.join(outputDir, 'functions.manifest.json'),
|
|
168
|
+
TF_VAR_app_name: appName ?? path.basename(projectPath).toLowerCase().replace(/[^a-z0-9-]/g, '-'),
|
|
169
|
+
TF_VAR_env: environment,
|
|
170
|
+
TF_VAR_nodejs_version: nodejsVersion,
|
|
171
|
+
};
|
|
172
|
+
if (cloudId)
|
|
173
|
+
tfVarEnv['TF_VAR_cloud_id'] = cloudId;
|
|
174
|
+
if (folderId)
|
|
175
|
+
tfVarEnv['TF_VAR_folder_id'] = folderId;
|
|
176
|
+
if (iamToken)
|
|
177
|
+
tfVarEnv['TF_VAR_iam_token'] = iamToken;
|
|
178
|
+
if (resolvedAccessKey)
|
|
179
|
+
tfVarEnv['TF_VAR_storage_access_key'] = resolvedAccessKey;
|
|
180
|
+
if (resolvedSecretKey)
|
|
181
|
+
tfVarEnv['TF_VAR_storage_secret_key'] = resolvedSecretKey;
|
|
182
|
+
if (deployBucket)
|
|
183
|
+
tfVarEnv['TF_VAR_deploy_bucket_name'] = deployBucket;
|
|
184
|
+
if (domainName)
|
|
185
|
+
tfVarEnv['TF_VAR_domain_name'] = domainName;
|
|
186
|
+
await terraform.apply({ autoApprove, env: tfVarEnv });
|
|
187
|
+
// Print outputs
|
|
188
|
+
const outputs = await terraform.readOutputs(tfVarEnv);
|
|
189
|
+
const url = extractOutputString(outputs, 'api_gateway_url');
|
|
190
|
+
if (url) {
|
|
191
|
+
console.log(chalk.green(`\nDeploy complete: ${url}`));
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
194
|
+
console.log(chalk.green('\nDeploy complete'));
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
catch (error) {
|
|
198
|
+
console.error(chalk.red('Deploy failed:'), error instanceof Error ? error.message : String(error));
|
|
199
|
+
process.exit(1);
|
|
200
|
+
}
|
|
201
|
+
finally {
|
|
202
|
+
await cleanupTerraformProject(terraformDir);
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
program.parse(process.argv);
|
|
206
|
+
if (!process.argv.slice(2).length) {
|
|
207
|
+
program.outputHelp();
|
|
208
|
+
}
|
|
209
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export interface TerraformBackendConfig {
|
|
2
|
+
bucket: string;
|
|
3
|
+
key: string;
|
|
4
|
+
region: string;
|
|
5
|
+
endpoint: string;
|
|
6
|
+
accessKey: string;
|
|
7
|
+
secretKey: string;
|
|
8
|
+
}
|
|
9
|
+
export interface TerraformBackendInput {
|
|
10
|
+
stateBucket?: string;
|
|
11
|
+
stateKey?: string;
|
|
12
|
+
stateRegion?: string;
|
|
13
|
+
stateEndpoint?: string;
|
|
14
|
+
stateAccessKey?: string;
|
|
15
|
+
stateSecretKey?: string;
|
|
16
|
+
}
|
|
17
|
+
export interface TerraformRunOptions {
|
|
18
|
+
captureOutput?: boolean;
|
|
19
|
+
env?: NodeJS.ProcessEnv;
|
|
20
|
+
}
|
|
21
|
+
export interface TerraformApplyOptions {
|
|
22
|
+
targets?: string[];
|
|
23
|
+
replace?: string[];
|
|
24
|
+
autoApprove?: boolean;
|
|
25
|
+
refresh?: boolean;
|
|
26
|
+
env?: NodeJS.ProcessEnv;
|
|
27
|
+
}
|
|
28
|
+
interface TerraformOutputEntry {
|
|
29
|
+
sensitive?: boolean;
|
|
30
|
+
type?: unknown;
|
|
31
|
+
value: unknown;
|
|
32
|
+
}
|
|
33
|
+
export declare function resolveBackendConfig(input: TerraformBackendInput, env?: NodeJS.ProcessEnv): TerraformBackendConfig | null;
|
|
34
|
+
export declare function extractOutputString(outputs: Record<string, TerraformOutputEntry>, key: string): string | undefined;
|
|
35
|
+
export declare function prepareTerraformProject(): Promise<string>;
|
|
36
|
+
export declare function cleanupTerraformProject(terraformDir: string): Promise<void>;
|
|
37
|
+
export declare class TerraformRunner {
|
|
38
|
+
private readonly terraformDir;
|
|
39
|
+
private readonly terraformBin;
|
|
40
|
+
constructor(terraformDir: string, terraformBin?: string);
|
|
41
|
+
init(backend?: TerraformBackendConfig, env?: NodeJS.ProcessEnv): Promise<void>;
|
|
42
|
+
apply(options?: TerraformApplyOptions): Promise<void>;
|
|
43
|
+
readOutputs(env?: NodeJS.ProcessEnv): Promise<Record<string, TerraformOutputEntry>>;
|
|
44
|
+
private run;
|
|
45
|
+
}
|
|
46
|
+
export {};
|
|
47
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/terraform/index.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,qBAAqB;IACpC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,mBAAmB;IAClC,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;CACzB;AAED,MAAM,WAAW,qBAAqB;IACpC,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;CACzB;AAED,UAAU,oBAAoB;IAC5B,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,KAAK,EAAE,OAAO,CAAC;CAChB;AAKD,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,qBAAqB,EAC5B,GAAG,GAAE,MAAM,CAAC,UAAwB,GACnC,sBAAsB,GAAG,IAAI,CAsB/B;AAED,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,oBAAoB,CAAC,EAC7C,GAAG,EAAE,MAAM,GACV,MAAM,GAAG,SAAS,CAOpB;AAED,wBAAsB,uBAAuB,IAAI,OAAO,CAAC,MAAM,CAAC,CAQ/D;AAED,wBAAsB,uBAAuB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAEjF;AAED,qBAAa,eAAe;IAExB,OAAO,CAAC,QAAQ,CAAC,YAAY;IAC7B,OAAO,CAAC,QAAQ,CAAC,YAAY;gBADZ,YAAY,EAAE,MAAM,EACpB,YAAY,GAAE,MAAoB;IAG/C,IAAI,CAAC,OAAO,CAAC,EAAE,sBAAsB,EAAE,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAmB9E,KAAK,CAAC,OAAO,GAAE,qBAA0B,GAAG,OAAO,CAAC,IAAI,CAAC;IAsBzD,WAAW,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,oBAAoB,CAAC,CAAC;YAiB3E,GAAG;CA2ClB"}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import fs from 'fs-extra';
|
|
2
|
+
import os from 'os';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { spawn } from 'child_process';
|
|
5
|
+
import { fileURLToPath } from 'url';
|
|
6
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
const TERRAFORM_TEMPLATE_DIR = path.join(__dirname, 'project');
|
|
8
|
+
export function resolveBackendConfig(input, env = process.env) {
|
|
9
|
+
const bucket = input.stateBucket || env['TF_STATE_BUCKET'] || '';
|
|
10
|
+
const key = input.stateKey || env['TF_STATE_KEY'] || '';
|
|
11
|
+
if (!bucket || !key) {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
const region = input.stateRegion || env['YC_REGION'] || 'ru-central1';
|
|
15
|
+
const endpoint = input.stateEndpoint || env['TF_STATE_ENDPOINT'] || 'https://storage.yandexcloud.net';
|
|
16
|
+
const accessKey = input.stateAccessKey || env['YC_ACCESS_KEY'] || env['AWS_ACCESS_KEY_ID'] || '';
|
|
17
|
+
const secretKey = input.stateSecretKey || env['YC_SECRET_KEY'] || env['AWS_SECRET_ACCESS_KEY'] || '';
|
|
18
|
+
if (!accessKey || !secretKey) {
|
|
19
|
+
throw new Error('Backend credentials are required: provide YC_ACCESS_KEY/YC_SECRET_KEY (or AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY).');
|
|
20
|
+
}
|
|
21
|
+
return { bucket, key, region, endpoint, accessKey, secretKey };
|
|
22
|
+
}
|
|
23
|
+
export function extractOutputString(outputs, key) {
|
|
24
|
+
const entry = outputs[key];
|
|
25
|
+
if (!entry)
|
|
26
|
+
return undefined;
|
|
27
|
+
if (entry.value === null || entry.value === undefined)
|
|
28
|
+
return undefined;
|
|
29
|
+
const value = String(entry.value).trim();
|
|
30
|
+
if (value === '' || value === 'null')
|
|
31
|
+
return undefined;
|
|
32
|
+
return value;
|
|
33
|
+
}
|
|
34
|
+
export async function prepareTerraformProject() {
|
|
35
|
+
if (!(await fs.pathExists(TERRAFORM_TEMPLATE_DIR))) {
|
|
36
|
+
throw new Error(`Embedded terraform template not found: ${TERRAFORM_TEMPLATE_DIR}`);
|
|
37
|
+
}
|
|
38
|
+
const workingDir = await fs.mkdtemp(path.join(os.tmpdir(), 'functions-yc-terraform-'));
|
|
39
|
+
await fs.copy(TERRAFORM_TEMPLATE_DIR, workingDir);
|
|
40
|
+
return workingDir;
|
|
41
|
+
}
|
|
42
|
+
export async function cleanupTerraformProject(terraformDir) {
|
|
43
|
+
await fs.remove(terraformDir);
|
|
44
|
+
}
|
|
45
|
+
export class TerraformRunner {
|
|
46
|
+
terraformDir;
|
|
47
|
+
terraformBin;
|
|
48
|
+
constructor(terraformDir, terraformBin = 'terraform') {
|
|
49
|
+
this.terraformDir = terraformDir;
|
|
50
|
+
this.terraformBin = terraformBin;
|
|
51
|
+
}
|
|
52
|
+
async init(backend, env) {
|
|
53
|
+
const args = ['init'];
|
|
54
|
+
if (backend) {
|
|
55
|
+
args.push(`-backend-config=bucket=${backend.bucket}`);
|
|
56
|
+
args.push(`-backend-config=key=${backend.key}`);
|
|
57
|
+
args.push(`-backend-config=region=${backend.region}`);
|
|
58
|
+
args.push(`-backend-config=endpoint=${backend.endpoint}`);
|
|
59
|
+
args.push('-backend-config=skip_region_validation=true');
|
|
60
|
+
args.push('-backend-config=skip_credentials_validation=true');
|
|
61
|
+
args.push('-backend-config=skip_metadata_api_check=true');
|
|
62
|
+
args.push('-backend-config=skip_requesting_account_id=true');
|
|
63
|
+
args.push(`-backend-config=access_key=${backend.accessKey}`);
|
|
64
|
+
args.push(`-backend-config=secret_key=${backend.secretKey}`);
|
|
65
|
+
}
|
|
66
|
+
await this.run(args, { env });
|
|
67
|
+
}
|
|
68
|
+
async apply(options = {}) {
|
|
69
|
+
const args = ['apply'];
|
|
70
|
+
if (options.autoApprove) {
|
|
71
|
+
args.push('-auto-approve');
|
|
72
|
+
}
|
|
73
|
+
if (options.refresh === false) {
|
|
74
|
+
args.push('-refresh=false');
|
|
75
|
+
}
|
|
76
|
+
for (const target of options.targets || []) {
|
|
77
|
+
args.push(`-target=${target}`);
|
|
78
|
+
}
|
|
79
|
+
for (const replaceTarget of options.replace || []) {
|
|
80
|
+
args.push(`-replace=${replaceTarget}`);
|
|
81
|
+
}
|
|
82
|
+
await this.run(args, { env: options.env });
|
|
83
|
+
}
|
|
84
|
+
async readOutputs(env) {
|
|
85
|
+
try {
|
|
86
|
+
const { stdout } = await this.run(['output', '-json'], { captureOutput: true, env });
|
|
87
|
+
if (!stdout.trim())
|
|
88
|
+
return {};
|
|
89
|
+
return JSON.parse(stdout);
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
93
|
+
if (message.includes('No outputs found') ||
|
|
94
|
+
message.includes('state file either has no outputs defined')) {
|
|
95
|
+
return {};
|
|
96
|
+
}
|
|
97
|
+
throw error;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
async run(args, options = {}) {
|
|
101
|
+
const captureOutput = options.captureOutput ?? false;
|
|
102
|
+
const MAX_CAPTURED_OUTPUT = 256 * 1024;
|
|
103
|
+
return new Promise((resolve, reject) => {
|
|
104
|
+
const child = spawn(this.terraformBin, args, {
|
|
105
|
+
cwd: this.terraformDir,
|
|
106
|
+
env: { ...process.env, ...options.env },
|
|
107
|
+
stdio: 'pipe',
|
|
108
|
+
});
|
|
109
|
+
let stdout = '';
|
|
110
|
+
let stderr = '';
|
|
111
|
+
const appendOutput = (current, chunk) => {
|
|
112
|
+
const next = current + String(chunk);
|
|
113
|
+
return next.length <= MAX_CAPTURED_OUTPUT ? next : next.slice(-MAX_CAPTURED_OUTPUT);
|
|
114
|
+
};
|
|
115
|
+
child.stdout?.on('data', (chunk) => {
|
|
116
|
+
stdout = appendOutput(stdout, chunk);
|
|
117
|
+
if (!captureOutput)
|
|
118
|
+
process.stdout.write(chunk);
|
|
119
|
+
});
|
|
120
|
+
child.stderr?.on('data', (chunk) => {
|
|
121
|
+
stderr = appendOutput(stderr, chunk);
|
|
122
|
+
if (!captureOutput)
|
|
123
|
+
process.stderr.write(chunk);
|
|
124
|
+
});
|
|
125
|
+
child.on('error', (err) => reject(err));
|
|
126
|
+
child.on('close', (code) => {
|
|
127
|
+
if (code === 0) {
|
|
128
|
+
resolve({ stdout, stderr });
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
const command = [this.terraformBin, ...args].join(' ');
|
|
132
|
+
reject(new Error(`Command failed (${code}): ${command}\n${stderr || stdout}`));
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/terraform/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
locals {
|
|
2
|
+
manifest = jsondecode(file(var.manifest_path))
|
|
3
|
+
app_id = "${var.app_name}-${var.env}"
|
|
4
|
+
functions_map = { for fn in local.manifest.functions : fn.name => fn }
|
|
5
|
+
deploy_bucket = trimspace(var.deploy_bucket_name) != "" ? var.deploy_bucket_name : "${local.app_id}-deploy"
|
|
6
|
+
has_domain = trimspace(var.domain_name) != ""
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
# ── Deploy bucket ─────────────────────────────────────────────────────────────
|
|
10
|
+
|
|
11
|
+
resource "yandex_storage_bucket" "deploy" {
|
|
12
|
+
bucket = local.deploy_bucket
|
|
13
|
+
acl = "private"
|
|
14
|
+
|
|
15
|
+
versioning {
|
|
16
|
+
enabled = false
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
# ── Service account ───────────────────────────────────────────────────────────
|
|
21
|
+
|
|
22
|
+
resource "yandex_iam_service_account" "sa" {
|
|
23
|
+
name = "${local.app_id}-sa"
|
|
24
|
+
description = "Service account for ${var.app_name} functions"
|
|
25
|
+
folder_id = var.folder_id
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
resource "yandex_resourcemanager_folder_iam_member" "invoker" {
|
|
29
|
+
folder_id = var.folder_id
|
|
30
|
+
role = "serverless.functions.invoker"
|
|
31
|
+
member = "serviceAccount:${yandex_iam_service_account.sa.id}"
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
resource "yandex_resourcemanager_folder_iam_member" "storage_viewer" {
|
|
35
|
+
folder_id = var.folder_id
|
|
36
|
+
role = "storage.viewer"
|
|
37
|
+
member = "serviceAccount:${yandex_iam_service_account.sa.id}"
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
# ── Cloud Functions ───────────────────────────────────────────────────────────
|
|
41
|
+
|
|
42
|
+
resource "yandex_function" "fn" {
|
|
43
|
+
for_each = local.functions_map
|
|
44
|
+
|
|
45
|
+
name = "${local.app_id}-${each.key}-fn"
|
|
46
|
+
description = "Function ${each.key} for ${var.app_name}"
|
|
47
|
+
folder_id = var.folder_id
|
|
48
|
+
runtime = var.nodejs_version
|
|
49
|
+
entrypoint = each.value.entry
|
|
50
|
+
memory = try(each.value.memory, var.function_memory)
|
|
51
|
+
execution_timeout = tostring(try(each.value.timeout, var.function_timeout))
|
|
52
|
+
|
|
53
|
+
user_hash = each.value.zipPath
|
|
54
|
+
|
|
55
|
+
package {
|
|
56
|
+
bucket_name = yandex_storage_bucket.deploy.bucket
|
|
57
|
+
object_name = "functions/${each.value.zipPath}"
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
service_account_id = yandex_iam_service_account.sa.id
|
|
61
|
+
|
|
62
|
+
environment = try(each.value.env, { NODE_ENV = "production" })
|
|
63
|
+
|
|
64
|
+
log_options {
|
|
65
|
+
disabled = false
|
|
66
|
+
min_level = "WARN"
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
depends_on = [yandex_storage_bucket.deploy]
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
# ── API Gateway ───────────────────────────────────────────────────────────────
|
|
73
|
+
|
|
74
|
+
locals {
|
|
75
|
+
# Build OpenAPI paths inline using actual function IDs
|
|
76
|
+
openapi_paths = merge([
|
|
77
|
+
for fn in local.manifest.functions : {
|
|
78
|
+
(fn.route) = {
|
|
79
|
+
"x-yc-apigateway-any-method" = {
|
|
80
|
+
operationId = fn.name
|
|
81
|
+
parameters = [
|
|
82
|
+
for p in fn.params : {
|
|
83
|
+
name = p
|
|
84
|
+
in = "path"
|
|
85
|
+
required = true
|
|
86
|
+
schema = { type = "string" }
|
|
87
|
+
}
|
|
88
|
+
]
|
|
89
|
+
"x-yc-apigateway-integration" = {
|
|
90
|
+
type = "cloud_functions"
|
|
91
|
+
function_id = yandex_function.fn[fn.name].id
|
|
92
|
+
service_account_id = yandex_iam_service_account.sa.id
|
|
93
|
+
payload_format_version = "1.0"
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
]...)
|
|
99
|
+
|
|
100
|
+
openapi_spec = jsonencode({
|
|
101
|
+
openapi = "3.0.0"
|
|
102
|
+
info = { title = var.app_name, version = "1.0.0" }
|
|
103
|
+
paths = local.openapi_paths
|
|
104
|
+
})
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
resource "yandex_api_gateway" "main" {
|
|
108
|
+
name = "${local.app_id}-apigw"
|
|
109
|
+
description = "API Gateway for ${var.app_name}"
|
|
110
|
+
folder_id = var.folder_id
|
|
111
|
+
|
|
112
|
+
spec = local.openapi_spec
|
|
113
|
+
|
|
114
|
+
dynamic "custom_domains" {
|
|
115
|
+
for_each = local.has_domain ? [1] : []
|
|
116
|
+
content {
|
|
117
|
+
fqdn = var.domain_name
|
|
118
|
+
certificate_id = local.effective_cert_id
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
depends_on = [yandex_function.fn]
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
# ── TLS Certificate ───────────────────────────────────────────────────────────
|
|
126
|
+
|
|
127
|
+
resource "yandex_cm_certificate" "main" {
|
|
128
|
+
count = local.has_domain && trimspace(var.certificate_id) == "" ? 1 : 0
|
|
129
|
+
name = "${local.app_id}-cert"
|
|
130
|
+
folder_id = var.folder_id
|
|
131
|
+
|
|
132
|
+
domains = [var.domain_name]
|
|
133
|
+
|
|
134
|
+
managed {
|
|
135
|
+
challenge_type = "DNS_CNAME"
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
locals {
|
|
140
|
+
effective_cert_id = trimspace(var.certificate_id) != "" ? var.certificate_id : (
|
|
141
|
+
length(yandex_cm_certificate.main) > 0 ? yandex_cm_certificate.main[0].id : ""
|
|
142
|
+
)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
# ── DNS Zone ──────────────────────────────────────────────────────────────────
|
|
146
|
+
|
|
147
|
+
resource "yandex_dns_zone" "main" {
|
|
148
|
+
count = local.has_domain && var.create_dns_zone && trimspace(var.dns_zone_id) == "" ? 1 : 0
|
|
149
|
+
name = "${local.app_id}-zone"
|
|
150
|
+
zone = "${var.domain_name}."
|
|
151
|
+
public = true
|
|
152
|
+
folder_id = var.folder_id
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
locals {
|
|
156
|
+
effective_dns_zone_id = trimspace(var.dns_zone_id) != "" ? var.dns_zone_id : (
|
|
157
|
+
length(yandex_dns_zone.main) > 0 ? yandex_dns_zone.main[0].id : ""
|
|
158
|
+
)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
resource "yandex_dns_recordset" "apigw_cname" {
|
|
162
|
+
count = local.has_domain && local.effective_dns_zone_id != "" ? 1 : 0
|
|
163
|
+
zone_id = local.effective_dns_zone_id
|
|
164
|
+
name = "${var.domain_name}."
|
|
165
|
+
type = "CNAME"
|
|
166
|
+
ttl = 300
|
|
167
|
+
data = ["${yandex_api_gateway.main.domain}."]
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
resource "yandex_dns_recordset" "cert_validation" {
|
|
171
|
+
count = (
|
|
172
|
+
local.has_domain &&
|
|
173
|
+
local.effective_dns_zone_id != "" &&
|
|
174
|
+
length(yandex_cm_certificate.main) > 0 &&
|
|
175
|
+
length(yandex_cm_certificate.main[0].challenges) > 0
|
|
176
|
+
) ? 1 : 0
|
|
177
|
+
|
|
178
|
+
zone_id = local.effective_dns_zone_id
|
|
179
|
+
name = yandex_cm_certificate.main[0].challenges[0].dns_name
|
|
180
|
+
type = yandex_cm_certificate.main[0].challenges[0].dns_type
|
|
181
|
+
ttl = 60
|
|
182
|
+
data = [yandex_cm_certificate.main[0].challenges[0].dns_value]
|
|
183
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
output "api_gateway_domain" {
|
|
2
|
+
description = "Default API Gateway domain"
|
|
3
|
+
value = yandex_api_gateway.main.domain
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
output "api_gateway_id" {
|
|
7
|
+
description = "API Gateway ID"
|
|
8
|
+
value = yandex_api_gateway.main.id
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
output "api_gateway_url" {
|
|
12
|
+
description = "API Gateway URL"
|
|
13
|
+
value = "https://${yandex_api_gateway.main.domain}"
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
output "custom_domain" {
|
|
17
|
+
description = "Custom domain (if configured)"
|
|
18
|
+
value = local.has_domain ? var.domain_name : null
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
output "deploy_bucket" {
|
|
22
|
+
description = "Object Storage bucket for function artifacts"
|
|
23
|
+
value = yandex_storage_bucket.deploy.bucket
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
output "service_account_id" {
|
|
27
|
+
description = "Service account ID for functions"
|
|
28
|
+
value = yandex_iam_service_account.sa.id
|
|
29
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
provider "yandex" {
|
|
2
|
+
token = var.iam_token
|
|
3
|
+
cloud_id = var.cloud_id
|
|
4
|
+
folder_id = var.folder_id
|
|
5
|
+
zone = var.zone
|
|
6
|
+
storage_access_key = trimspace(var.storage_access_key) != "" ? var.storage_access_key : null
|
|
7
|
+
storage_secret_key = trimspace(var.storage_secret_key) != "" ? var.storage_secret_key : null
|
|
8
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
variable "cloud_id" {
|
|
2
|
+
description = "Yandex Cloud ID"
|
|
3
|
+
type = string
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
variable "folder_id" {
|
|
7
|
+
description = "Yandex Cloud folder ID"
|
|
8
|
+
type = string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
variable "iam_token" {
|
|
12
|
+
description = "Yandex Cloud IAM token"
|
|
13
|
+
type = string
|
|
14
|
+
sensitive = true
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
variable "app_name" {
|
|
18
|
+
description = "Application name (lowercase alphanumeric + hyphens, 3-31 chars)"
|
|
19
|
+
type = string
|
|
20
|
+
|
|
21
|
+
validation {
|
|
22
|
+
condition = can(regex("^[a-z0-9][a-z0-9-]{1,29}[a-z0-9]$", var.app_name))
|
|
23
|
+
error_message = "app_name must be 3-31 lowercase alphanumeric characters or hyphens."
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
variable "env" {
|
|
28
|
+
description = "Deployment environment (dev, staging, production)"
|
|
29
|
+
type = string
|
|
30
|
+
default = "production"
|
|
31
|
+
|
|
32
|
+
validation {
|
|
33
|
+
condition = contains(["dev", "staging", "production"], var.env)
|
|
34
|
+
error_message = "env must be one of: dev, staging, production."
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
variable "region" {
|
|
39
|
+
description = "Yandex Cloud region"
|
|
40
|
+
type = string
|
|
41
|
+
default = "ru-central1"
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
variable "zone" {
|
|
45
|
+
description = "Yandex Cloud availability zone"
|
|
46
|
+
type = string
|
|
47
|
+
default = "ru-central1-a"
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
variable "nodejs_version" {
|
|
51
|
+
description = "Node.js runtime version for Cloud Functions"
|
|
52
|
+
type = string
|
|
53
|
+
default = "nodejs20"
|
|
54
|
+
|
|
55
|
+
validation {
|
|
56
|
+
condition = contains(["nodejs18", "nodejs20", "nodejs22"], var.nodejs_version)
|
|
57
|
+
error_message = "nodejs_version must be one of: nodejs18, nodejs20, nodejs22."
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
variable "function_memory" {
|
|
62
|
+
description = "Memory (MB) for Cloud Functions"
|
|
63
|
+
type = number
|
|
64
|
+
default = 256
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
variable "function_timeout" {
|
|
68
|
+
description = "Execution timeout (seconds) for Cloud Functions"
|
|
69
|
+
type = number
|
|
70
|
+
default = 30
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
variable "storage_access_key" {
|
|
74
|
+
description = "Yandex Object Storage access key"
|
|
75
|
+
type = string
|
|
76
|
+
default = ""
|
|
77
|
+
sensitive = true
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
variable "storage_secret_key" {
|
|
81
|
+
description = "Yandex Object Storage secret key"
|
|
82
|
+
type = string
|
|
83
|
+
default = ""
|
|
84
|
+
sensitive = true
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
variable "deploy_bucket_name" {
|
|
88
|
+
description = "Bucket for storing function artifacts (created if empty)"
|
|
89
|
+
type = string
|
|
90
|
+
default = ""
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
variable "manifest_path" {
|
|
94
|
+
description = "Path to the functions.manifest.json file"
|
|
95
|
+
type = string
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
variable "domain_name" {
|
|
99
|
+
description = "Custom domain for the API Gateway"
|
|
100
|
+
type = string
|
|
101
|
+
default = ""
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
variable "dns_zone_id" {
|
|
105
|
+
description = "Existing DNS zone ID (created if empty and domain_name is set)"
|
|
106
|
+
type = string
|
|
107
|
+
default = ""
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
variable "certificate_id" {
|
|
111
|
+
description = "Existing TLS certificate ID (created if empty and domain_name is set)"
|
|
112
|
+
type = string
|
|
113
|
+
default = ""
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
variable "create_dns_zone" {
|
|
117
|
+
description = "Create a new DNS zone for domain_name"
|
|
118
|
+
type = bool
|
|
119
|
+
default = false
|
|
120
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { FunctionsManifest } from '../build/index.js';
|
|
2
|
+
export interface UploadOptions {
|
|
3
|
+
outputDir: string;
|
|
4
|
+
manifest: FunctionsManifest;
|
|
5
|
+
bucket: string;
|
|
6
|
+
accessKey: string;
|
|
7
|
+
secretKey: string;
|
|
8
|
+
region?: string;
|
|
9
|
+
endpoint?: string;
|
|
10
|
+
dryRun?: boolean;
|
|
11
|
+
verbose?: boolean;
|
|
12
|
+
}
|
|
13
|
+
export declare class Uploader {
|
|
14
|
+
upload(options: UploadOptions): Promise<void>;
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/upload/index.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAE3D,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,iBAAiB,CAAC;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,qBAAa,QAAQ;IACb,MAAM,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;CAgDpD"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import fs from 'fs-extra';
|
|
3
|
+
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';
|
|
4
|
+
import { Upload } from '@aws-sdk/lib-storage';
|
|
5
|
+
export class Uploader {
|
|
6
|
+
async upload(options) {
|
|
7
|
+
const { outputDir, manifest, bucket, accessKey, secretKey, region, endpoint, dryRun, verbose } = options;
|
|
8
|
+
const s3 = new S3Client({
|
|
9
|
+
region: region ?? 'ru-central1',
|
|
10
|
+
endpoint: endpoint ?? 'https://storage.yandexcloud.net',
|
|
11
|
+
credentials: { accessKeyId: accessKey, secretAccessKey: secretKey },
|
|
12
|
+
});
|
|
13
|
+
for (const fn of manifest.functions) {
|
|
14
|
+
const zipPath = path.join(outputDir, fn.zipPath);
|
|
15
|
+
const key = `functions/${fn.zipPath}`;
|
|
16
|
+
if (verbose) {
|
|
17
|
+
console.log(` Uploading ${fn.zipPath} → s3://${bucket}/${key}`);
|
|
18
|
+
}
|
|
19
|
+
if (!dryRun) {
|
|
20
|
+
const fileStream = fs.createReadStream(zipPath);
|
|
21
|
+
const upload = new Upload({
|
|
22
|
+
client: s3,
|
|
23
|
+
params: { Bucket: bucket, Key: key, Body: fileStream },
|
|
24
|
+
});
|
|
25
|
+
await upload.done();
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
// Upload manifest
|
|
29
|
+
const manifestPath = path.join(outputDir, 'functions.manifest.json');
|
|
30
|
+
const manifestKey = 'functions.manifest.json';
|
|
31
|
+
if (verbose) {
|
|
32
|
+
console.log(` Uploading manifest → s3://${bucket}/${manifestKey}`);
|
|
33
|
+
}
|
|
34
|
+
if (!dryRun) {
|
|
35
|
+
const content = await fs.readFile(manifestPath);
|
|
36
|
+
await s3.send(new PutObjectCommand({
|
|
37
|
+
Bucket: bucket,
|
|
38
|
+
Key: manifestKey,
|
|
39
|
+
Body: content,
|
|
40
|
+
ContentType: 'application/json',
|
|
41
|
+
}));
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/upload/index.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,MAAM,UAAU,CAAC;AAC1B,OAAO,EAAE,QAAQ,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAChE,OAAO,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAe9C,MAAM,OAAO,QAAQ;IACnB,KAAK,CAAC,MAAM,CAAC,OAAsB;QACjC,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,GAC5F,OAAO,CAAC;QAEV,MAAM,EAAE,GAAG,IAAI,QAAQ,CAAC;YACtB,MAAM,EAAE,MAAM,IAAI,aAAa;YAC/B,QAAQ,EAAE,QAAQ,IAAI,iCAAiC;YACvD,WAAW,EAAE,EAAE,WAAW,EAAE,SAAS,EAAE,eAAe,EAAE,SAAS,EAAE;SACpE,CAAC,CAAC;QAEH,KAAK,MAAM,EAAE,IAAI,QAAQ,CAAC,SAAS,EAAE,CAAC;YACpC,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC;YACjD,MAAM,GAAG,GAAG,aAAa,EAAE,CAAC,OAAO,EAAE,CAAC;YAEtC,IAAI,OAAO,EAAE,CAAC;gBACZ,OAAO,CAAC,GAAG,CAAC,eAAe,EAAE,CAAC,OAAO,WAAW,MAAM,IAAI,GAAG,EAAE,CAAC,CAAC;YACnE,CAAC;YAED,IAAI,CAAC,MAAM,EAAE,CAAC;gBACZ,MAAM,UAAU,GAAG,EAAE,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC;gBAChD,MAAM,MAAM,GAAG,IAAI,MAAM,CAAC;oBACxB,MAAM,EAAE,EAAE;oBACV,MAAM,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,UAAU,EAAE;iBACvD,CAAC,CAAC;gBACH,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC;YACtB,CAAC;QACH,CAAC;QAED,kBAAkB;QAClB,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,yBAAyB,CAAC,CAAC;QACrE,MAAM,WAAW,GAAG,yBAAyB,CAAC;QAE9C,IAAI,OAAO,EAAE,CAAC;YACZ,OAAO,CAAC,GAAG,CAAC,+BAA+B,MAAM,IAAI,WAAW,EAAE,CAAC,CAAC;QACtE,CAAC;QAED,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,MAAM,OAAO,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC;YAChD,MAAM,EAAE,CAAC,IAAI,CACX,IAAI,gBAAgB,CAAC;gBACnB,MAAM,EAAE,MAAM;gBACd,GAAG,EAAE,WAAW;gBAChB,IAAI,EAAE,OAAO;gBACb,WAAW,EAAE,kBAAkB;aAChC,CAAC,CACH,CAAC;QACJ,CAAC;IACH,CAAC;CACF"}
|
package/package.json
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@yc-tools/functions-yc",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "CLI tool for deploying TypeScript functions to Yandex Cloud",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "yc-tools Contributors",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/yc-tools/functions-yc.git"
|
|
10
|
+
},
|
|
11
|
+
"publishConfig": {
|
|
12
|
+
"registry": "https://registry.npmjs.org",
|
|
13
|
+
"access": "public",
|
|
14
|
+
"provenance": true
|
|
15
|
+
},
|
|
16
|
+
"type": "module",
|
|
17
|
+
"main": "./dist/index.js",
|
|
18
|
+
"bin": {
|
|
19
|
+
"functions-yc": "./dist/index.js"
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist",
|
|
23
|
+
"README.md"
|
|
24
|
+
],
|
|
25
|
+
"scripts": {
|
|
26
|
+
"build": "tsc && cp -r src/terraform/project dist/terraform/project && chmod +x dist/index.js",
|
|
27
|
+
"dev": "tsc --watch",
|
|
28
|
+
"clean": "rm -rf dist",
|
|
29
|
+
"lint": "tsc --noEmit",
|
|
30
|
+
"prepublishOnly": "npm run clean && npm run build"
|
|
31
|
+
},
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"@aws-sdk/client-s3": "^3.490.0",
|
|
34
|
+
"@aws-sdk/lib-storage": "^3.490.0",
|
|
35
|
+
"archiver": "^6.0.1",
|
|
36
|
+
"chalk": "^5.3.0",
|
|
37
|
+
"commander": "^11.1.0",
|
|
38
|
+
"esbuild": "^0.19.11",
|
|
39
|
+
"fs-extra": "^11.2.0",
|
|
40
|
+
"glob": "^11.0.1",
|
|
41
|
+
"ora": "^8.0.1"
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"@types/archiver": "^6.0.2",
|
|
45
|
+
"@types/fs-extra": "^11.0.4",
|
|
46
|
+
"@types/node": "^20.0.0",
|
|
47
|
+
"typescript": "^5.3.3"
|
|
48
|
+
},
|
|
49
|
+
"keywords": [
|
|
50
|
+
"yandex-cloud",
|
|
51
|
+
"serverless",
|
|
52
|
+
"functions",
|
|
53
|
+
"deploy",
|
|
54
|
+
"terraform"
|
|
55
|
+
],
|
|
56
|
+
"engines": {
|
|
57
|
+
"node": ">=20.0.0"
|
|
58
|
+
}
|
|
59
|
+
}
|