@voxgig/build 4.1.0 → 4.2.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/build.ts +13 -0
- package/dist/build.d.ts +18 -1
- package/dist/build.js +11 -1
- package/dist/build.js.map +1 -1
- package/dist/env/env_gen.d.ts +16 -0
- package/dist/env/env_gen.js +143 -0
- package/dist/env/env_gen.js.map +1 -0
- package/dist/env/lambda/generate.d.ts +3 -2
- package/dist/env/lambda/generate.js +13 -9
- package/dist/env/lambda/generate.js.map +1 -1
- package/env/env_gen.ts +165 -0
- package/env/lambda/generate.ts +15 -8
- package/package.json +1 -1
- package/tm/env/aws/README.md.frag +9 -0
- package/tm/env/aws/lambda.entry.ts.frag +89 -0
- package/tm/env/aws/serverless.yml.frag +23 -0
- package/tm/env/azure/README.md.frag +7 -0
- package/tm/env/azure/entry.ts.frag +49 -0
- package/tm/env/azure/host.json.frag +8 -0
- package/tm/env/basic/README.md.frag +7 -0
- package/tm/env/basic/backend.service.frag +14 -0
- package/tm/env/basic/deploy.sh.frag +13 -0
- package/tm/env/cloudflare/README.md.frag +7 -0
- package/tm/env/cloudflare/worker.ts.frag +13 -0
- package/tm/env/cloudflare/wrangler.toml.frag +7 -0
- package/tm/env/docker/Dockerfile.frag +10 -0
- package/tm/env/docker/README.md.frag +7 -0
- package/tm/env/docker/compose.yml.frag +10 -0
- package/tm/env/local/README.md.frag +7 -0
- package/tm/env/local/run.sh.frag +6 -0
- package/tm/env/server.entry.ts.frag +48 -0
- package/tm/env/vm/README.md.frag +5 -0
- package/tm/env/vm/cloud-init.yaml.frag +23 -0
package/build.ts
CHANGED
|
@@ -17,6 +17,8 @@ import { srv_yml } from './env/lambda/srv_yml'
|
|
|
17
17
|
import { srv_handler } from './env/lambda/srv_handler'
|
|
18
18
|
import { resources_yml } from './env/lambda/res_yml'
|
|
19
19
|
|
|
20
|
+
import { env_gen, ENV_FILES, ENV_SRC, KINDS as ENV_KINDS } from './env/env_gen'
|
|
21
|
+
|
|
20
22
|
import {
|
|
21
23
|
generate, empty, TM,
|
|
22
24
|
loadFragment, renderFragment, listFragments, PKG_TM,
|
|
@@ -34,6 +36,16 @@ const EnvLambda = {
|
|
|
34
36
|
}
|
|
35
37
|
|
|
36
38
|
|
|
39
|
+
// Per-environment deployment artifacts (gen/env/<name>/), driven by the
|
|
40
|
+
// model's `main: env:` declarations. See env/env_gen.ts.
|
|
41
|
+
const EnvGen = {
|
|
42
|
+
env_gen,
|
|
43
|
+
files: ENV_FILES,
|
|
44
|
+
srcfiles: ENV_SRC,
|
|
45
|
+
kinds: ENV_KINDS,
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
|
|
37
49
|
// Fragment tooling (used by voxgig-system template list/eject/diff, and
|
|
38
50
|
// available to project code).
|
|
39
51
|
const Fragments = {
|
|
@@ -46,6 +58,7 @@ const Fragments = {
|
|
|
46
58
|
|
|
47
59
|
export {
|
|
48
60
|
EnvLambda,
|
|
61
|
+
EnvGen,
|
|
49
62
|
Fragments,
|
|
50
63
|
|
|
51
64
|
// Building blocks for ejected project templates (src/gen/<name>.ts
|
package/dist/build.d.ts
CHANGED
|
@@ -23,10 +23,27 @@ declare const EnvLambda: {
|
|
|
23
23
|
tm?: string;
|
|
24
24
|
}) => Promise<void>;
|
|
25
25
|
};
|
|
26
|
+
declare const EnvGen: {
|
|
27
|
+
env_gen: (model: any, spec: {
|
|
28
|
+
folder: string;
|
|
29
|
+
tm?: string;
|
|
30
|
+
src?: string;
|
|
31
|
+
}) => Promise<void>;
|
|
32
|
+
files: Record<string, {
|
|
33
|
+
frag: string;
|
|
34
|
+
out: string;
|
|
35
|
+
}[]>;
|
|
36
|
+
srcfiles: Record<string, {
|
|
37
|
+
frag: string;
|
|
38
|
+
dir: string;
|
|
39
|
+
out: string;
|
|
40
|
+
}[]>;
|
|
41
|
+
kinds: string[];
|
|
42
|
+
};
|
|
26
43
|
declare const Fragments: {
|
|
27
44
|
load: typeof loadFragment;
|
|
28
45
|
render: typeof renderFragment;
|
|
29
46
|
list: typeof listFragments;
|
|
30
47
|
folder: string;
|
|
31
48
|
};
|
|
32
|
-
export { EnvLambda, Fragments, generate, empty, TM, loadFragment, renderFragment, MsgMetaShape, CoreConfShape, CloudConfShape, res_dynamo_yml, };
|
|
49
|
+
export { EnvLambda, EnvGen, Fragments, generate, empty, TM, loadFragment, renderFragment, MsgMetaShape, CoreConfShape, CloudConfShape, res_dynamo_yml, };
|
package/dist/build.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/* Copyright © 2022-2026 Voxgig Ltd, MIT License. */
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
-
exports.res_dynamo_yml = exports.CloudConfShape = exports.CoreConfShape = exports.MsgMetaShape = exports.renderFragment = exports.loadFragment = exports.TM = exports.empty = exports.generate = exports.Fragments = exports.EnvLambda = void 0;
|
|
4
|
+
exports.res_dynamo_yml = exports.CloudConfShape = exports.CoreConfShape = exports.MsgMetaShape = exports.renderFragment = exports.loadFragment = exports.TM = exports.empty = exports.generate = exports.Fragments = exports.EnvGen = exports.EnvLambda = void 0;
|
|
5
5
|
// EnvLambda: generation of the Lambda deployment templates, built on the
|
|
6
6
|
// jostraca templating library. The templates live in env/lambda/, one file
|
|
7
7
|
// per output for convenience:
|
|
@@ -17,6 +17,7 @@ exports.res_dynamo_yml = exports.CloudConfShape = exports.CoreConfShape = export
|
|
|
17
17
|
const srv_yml_1 = require("./env/lambda/srv_yml");
|
|
18
18
|
const srv_handler_1 = require("./env/lambda/srv_handler");
|
|
19
19
|
const res_yml_1 = require("./env/lambda/res_yml");
|
|
20
|
+
const env_gen_1 = require("./env/env_gen");
|
|
20
21
|
const generate_1 = require("./env/lambda/generate");
|
|
21
22
|
Object.defineProperty(exports, "generate", { enumerable: true, get: function () { return generate_1.generate; } });
|
|
22
23
|
Object.defineProperty(exports, "empty", { enumerable: true, get: function () { return generate_1.empty; } });
|
|
@@ -36,6 +37,15 @@ const EnvLambda = {
|
|
|
36
37
|
resources_yml: res_yml_1.resources_yml,
|
|
37
38
|
};
|
|
38
39
|
exports.EnvLambda = EnvLambda;
|
|
40
|
+
// Per-environment deployment artifacts (gen/env/<name>/), driven by the
|
|
41
|
+
// model's `main: env:` declarations. See env/env_gen.ts.
|
|
42
|
+
const EnvGen = {
|
|
43
|
+
env_gen: env_gen_1.env_gen,
|
|
44
|
+
files: env_gen_1.ENV_FILES,
|
|
45
|
+
srcfiles: env_gen_1.ENV_SRC,
|
|
46
|
+
kinds: env_gen_1.KINDS,
|
|
47
|
+
};
|
|
48
|
+
exports.EnvGen = EnvGen;
|
|
39
49
|
// Fragment tooling (used by voxgig-system template list/eject/diff, and
|
|
40
50
|
// available to project code).
|
|
41
51
|
const Fragments = {
|
package/dist/build.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"build.js","sourceRoot":"","sources":["../build.ts"],"names":[],"mappings":";AAAA,oDAAoD;;;AAEpD,yEAAyE;AACzE,2EAA2E;AAC3E,8BAA8B;AAC9B,EAAE;AACF,yEAAyE;AACzE,uEAAuE;AACvE,yEAAyE;AACzE,EAAE;AACF,yEAAyE;AACzE,2EAA2E;AAC3E,wEAAwE;AACxE,qCAAqC;AAErC,kDAA8C;AAC9C,0DAAsD;AACtD,kDAAoD;AAEpD,oDAG8B;
|
|
1
|
+
{"version":3,"file":"build.js","sourceRoot":"","sources":["../build.ts"],"names":[],"mappings":";AAAA,oDAAoD;;;AAEpD,yEAAyE;AACzE,2EAA2E;AAC3E,8BAA8B;AAC9B,EAAE;AACF,yEAAyE;AACzE,uEAAuE;AACvE,yEAAyE;AACzE,EAAE;AACF,yEAAyE;AACzE,2EAA2E;AAC3E,wEAAwE;AACxE,qCAAqC;AAErC,kDAA8C;AAC9C,0DAAsD;AACtD,kDAAoD;AAEpD,2CAA+E;AAE/E,oDAG8B;AA0C5B,yFA5CA,mBAAQ,OA4CA;AACR,sFA7CU,gBAAK,OA6CV;AACL,mFA9CiB,aAAE,OA8CjB;AACF,6FA9CA,uBAAY,OA8CA;AACZ,+FA/Cc,yBAAc,OA+Cd;AA5ChB,qCAA0C;AA6CxC,6FA7CO,kBAAY,OA6CP;AA5Cd,uCAA4D;AA6C1D,8FA7CO,oBAAa,OA6CP;AACb,+FA9CsB,qBAAc,OA8CtB;AA7ChB,yDAAqD;AA8CnD,+FA9CO,+BAAc,OA8CP;AA3ChB,MAAM,SAAS,GAAG;IAChB,OAAO,EAAP,iBAAO;IACP,WAAW,EAAX,yBAAW;IACX,aAAa,EAAb,uBAAa;CACd,CAAA;AAwBC,8BAAS;AArBX,wEAAwE;AACxE,yDAAyD;AACzD,MAAM,MAAM,GAAG;IACb,OAAO,EAAP,iBAAO;IACP,KAAK,EAAE,mBAAS;IAChB,QAAQ,EAAE,iBAAO;IACjB,KAAK,EAAE,eAAS;CACjB,CAAA;AAeC,wBAAM;AAZR,wEAAwE;AACxE,8BAA8B;AAC9B,MAAM,SAAS,GAAG;IAChB,IAAI,EAAE,uBAAY;IAClB,MAAM,EAAE,yBAAc;IACtB,IAAI,EAAE,wBAAa;IACnB,MAAM,EAAE,iBAAM;CACf,CAAA;AAMC,8BAAS"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
declare const ENV_FILES: Record<string, {
|
|
2
|
+
frag: string;
|
|
3
|
+
out: string;
|
|
4
|
+
}[]>;
|
|
5
|
+
declare const ENV_SRC: Record<string, {
|
|
6
|
+
frag: string;
|
|
7
|
+
dir: string;
|
|
8
|
+
out: string;
|
|
9
|
+
}[]>;
|
|
10
|
+
declare const KINDS: string[];
|
|
11
|
+
declare const env_gen: (model: any, spec: {
|
|
12
|
+
folder: string;
|
|
13
|
+
tm?: string;
|
|
14
|
+
src?: string;
|
|
15
|
+
}) => Promise<void>;
|
|
16
|
+
export { env_gen, ENV_FILES, ENV_SRC, KINDS, };
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/* Copyright © 2026 Voxgig Ltd, MIT License. */
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.KINDS = exports.ENV_SRC = exports.ENV_FILES = exports.env_gen = void 0;
|
|
8
|
+
// EnvGen: per-environment deployment artifact generation. The project
|
|
9
|
+
// model declares its target environments under `main: env:`:
|
|
10
|
+
//
|
|
11
|
+
// main: env: local: { active: true }
|
|
12
|
+
// main: env: aws: { active: true, region: 'eu-west-1', stage: 'dev' }
|
|
13
|
+
//
|
|
14
|
+
// For each active environment this generates gen/env/<name>/ from the
|
|
15
|
+
// fragment set tm/env/<kind>/ (kind defaults to the env name, so a
|
|
16
|
+
// project can declare e.g. `aws2: { kind: 'aws', region: ... }`).
|
|
17
|
+
// Fragments are project-shadowable via spec.tm (the project's tm/env
|
|
18
|
+
// folder), like the lambda templates.
|
|
19
|
+
const fs_1 = __importDefault(require("fs"));
|
|
20
|
+
const path_1 = __importDefault(require("path"));
|
|
21
|
+
const util_1 = require("@voxgig/util");
|
|
22
|
+
const conf_1 = require("../shape/conf");
|
|
23
|
+
const generate_1 = require("./lambda/generate");
|
|
24
|
+
// The supported environment kinds and the files each generates.
|
|
25
|
+
// `out` may contain $$slots$$ (rendered with the same slot values).
|
|
26
|
+
const ENV_FILES = {
|
|
27
|
+
local: [
|
|
28
|
+
{ frag: 'local/run.sh.frag', out: 'run.sh' },
|
|
29
|
+
{ frag: 'local/README.md.frag', out: 'README.md' },
|
|
30
|
+
],
|
|
31
|
+
basic: [
|
|
32
|
+
{ frag: 'basic/backend.service.frag', out: '$$name$$-backend.service' },
|
|
33
|
+
{ frag: 'basic/deploy.sh.frag', out: 'deploy.sh' },
|
|
34
|
+
{ frag: 'basic/README.md.frag', out: 'README.md' },
|
|
35
|
+
],
|
|
36
|
+
docker: [
|
|
37
|
+
{ frag: 'docker/Dockerfile.frag', out: 'Dockerfile' },
|
|
38
|
+
{ frag: 'docker/compose.yml.frag', out: 'compose.yml' },
|
|
39
|
+
{ frag: 'docker/README.md.frag', out: 'README.md' },
|
|
40
|
+
],
|
|
41
|
+
vm: [
|
|
42
|
+
{ frag: 'vm/cloud-init.yaml.frag', out: 'cloud-init.yaml' },
|
|
43
|
+
{ frag: 'vm/README.md.frag', out: 'README.md' },
|
|
44
|
+
],
|
|
45
|
+
aws: [
|
|
46
|
+
{ frag: 'aws/serverless.yml.frag', out: 'serverless.yml' },
|
|
47
|
+
{ frag: 'aws/README.md.frag', out: 'README.md' },
|
|
48
|
+
],
|
|
49
|
+
azure: [
|
|
50
|
+
{ frag: 'azure/host.json.frag', out: 'host.json' },
|
|
51
|
+
{ frag: 'azure/README.md.frag', out: 'README.md' },
|
|
52
|
+
],
|
|
53
|
+
cloudflare: [
|
|
54
|
+
{ frag: 'cloudflare/wrangler.toml.frag', out: 'wrangler.toml' },
|
|
55
|
+
{ frag: 'cloudflare/README.md.frag', out: 'README.md' },
|
|
56
|
+
],
|
|
57
|
+
};
|
|
58
|
+
exports.ENV_FILES = ENV_FILES;
|
|
59
|
+
// The runtime entry point + Seneca config setup each environment needs
|
|
60
|
+
// under src/env/. These are generated ONCE (only when missing): after
|
|
61
|
+
// creation the project owns them, like the scaffold's local entry. All
|
|
62
|
+
// entries route Seneca configuration through src/env/shared/basic.ts.
|
|
63
|
+
// `dir` is relative to the project src/env folder.
|
|
64
|
+
const ENV_SRC = {
|
|
65
|
+
local: [], // scaffold provides src/env/local/local.ts
|
|
66
|
+
basic: [
|
|
67
|
+
{ frag: 'server.entry.ts.frag', dir: 'basic', out: 'basic.ts' },
|
|
68
|
+
],
|
|
69
|
+
docker: [
|
|
70
|
+
{ frag: 'server.entry.ts.frag', dir: 'docker', out: 'docker.ts' },
|
|
71
|
+
],
|
|
72
|
+
vm: [
|
|
73
|
+
{ frag: 'server.entry.ts.frag', dir: 'vm', out: 'vm.ts' },
|
|
74
|
+
],
|
|
75
|
+
aws: [
|
|
76
|
+
// The Lambda bootstrap the generated handlers import.
|
|
77
|
+
{ frag: 'aws/lambda.entry.ts.frag', dir: 'lambda', out: 'lambda.ts' },
|
|
78
|
+
],
|
|
79
|
+
azure: [
|
|
80
|
+
{ frag: 'azure/entry.ts.frag', dir: 'azure', out: 'azure.ts' },
|
|
81
|
+
],
|
|
82
|
+
cloudflare: [
|
|
83
|
+
{ frag: 'cloudflare/worker.ts.frag', dir: 'cloudflare', out: 'worker.ts' },
|
|
84
|
+
],
|
|
85
|
+
};
|
|
86
|
+
exports.ENV_SRC = ENV_SRC;
|
|
87
|
+
const KINDS = Object.keys(ENV_FILES);
|
|
88
|
+
exports.KINDS = KINDS;
|
|
89
|
+
const env_gen = async (model, spec) => {
|
|
90
|
+
var _a;
|
|
91
|
+
const core = (0, conf_1.CoreConfShape)(model.main.conf.core);
|
|
92
|
+
const envs = model.main.env || {};
|
|
93
|
+
for (const entry of Object.entries(envs)) {
|
|
94
|
+
const name = entry[0];
|
|
95
|
+
const def = entry[1] || {};
|
|
96
|
+
if (false === def.active) {
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
const kind = def.kind || name;
|
|
100
|
+
const filedefs = ENV_FILES[kind];
|
|
101
|
+
if (null == filedefs) {
|
|
102
|
+
throw new Error('@voxgig/build: unknown environment kind: ' + kind +
|
|
103
|
+
' (env ' + name + '; known kinds: ' + KINDS.join(', ') + ')');
|
|
104
|
+
}
|
|
105
|
+
// Slot values: project identity, ports, and per-env config with
|
|
106
|
+
// sensible defaults. Additional scalar config keys on the env
|
|
107
|
+
// definition become slots too.
|
|
108
|
+
const slots = {
|
|
109
|
+
name: core.name,
|
|
110
|
+
Name: (0, util_1.camelify)(core.name),
|
|
111
|
+
env: name,
|
|
112
|
+
port: ((_a = model.main.conf.port) === null || _a === void 0 ? void 0 : _a.backend) || 8080,
|
|
113
|
+
node: def.node || '22',
|
|
114
|
+
region: def.region || 'us-east-1',
|
|
115
|
+
stage: def.stage || 'dev',
|
|
116
|
+
};
|
|
117
|
+
for (const key of Object.keys(def)) {
|
|
118
|
+
if ('object' !== typeof def[key] && null == slots[key]) {
|
|
119
|
+
slots[key] = def[key];
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
// Slot for the env name inside entry fragments.
|
|
123
|
+
slots.env = name;
|
|
124
|
+
const files = filedefs.map((fd) => ({
|
|
125
|
+
name: (0, generate_1.renderFragment)(fd.out, slots),
|
|
126
|
+
content: (0, generate_1.renderFragment)((0, generate_1.loadFragment)(fd.frag, spec, 'env'), slots),
|
|
127
|
+
}));
|
|
128
|
+
await (0, generate_1.generate)(spec.folder + '/' + name, files);
|
|
129
|
+
// Runtime entries: create-once into src/env (user-owned afterwards).
|
|
130
|
+
if (null != spec.src) {
|
|
131
|
+
for (const sd of (ENV_SRC[kind] || [])) {
|
|
132
|
+
const dest = path_1.default.join(spec.src, sd.dir, (0, generate_1.renderFragment)(sd.out, slots));
|
|
133
|
+
if (!fs_1.default.existsSync(dest)) {
|
|
134
|
+
const content = (0, generate_1.renderFragment)((0, generate_1.loadFragment)(sd.frag, spec, 'env'), slots);
|
|
135
|
+
fs_1.default.mkdirSync(path_1.default.dirname(dest), { recursive: true });
|
|
136
|
+
fs_1.default.writeFileSync(dest, content);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
exports.env_gen = env_gen;
|
|
143
|
+
//# sourceMappingURL=env_gen.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"env_gen.js","sourceRoot":"","sources":["../../env/env_gen.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
declare const PKG_TM_ROOT: string;
|
|
1
2
|
declare const PKG_TM: string;
|
|
2
3
|
declare function loadFragment(name: string, spec?: {
|
|
3
4
|
tm?: string;
|
|
4
|
-
}): string;
|
|
5
|
+
}, area?: string): string;
|
|
5
6
|
declare function renderFragment(src: string, slots: Record<string, any>): string;
|
|
6
7
|
declare function listFragments(): string[];
|
|
7
8
|
declare function generate(folder: string, files: {
|
|
@@ -10,4 +11,4 @@ declare function generate(folder: string, files: {
|
|
|
10
11
|
}[]): Promise<void>;
|
|
11
12
|
declare function empty(o: any): boolean;
|
|
12
13
|
declare function TM(str: string): string;
|
|
13
|
-
export { generate, empty, TM, loadFragment, renderFragment, listFragments, PKG_TM, };
|
|
14
|
+
export { generate, empty, TM, loadFragment, renderFragment, listFragments, PKG_TM, PKG_TM_ROOT, };
|
|
@@ -4,7 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
5
|
};
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
exports.PKG_TM = void 0;
|
|
7
|
+
exports.PKG_TM_ROOT = exports.PKG_TM = void 0;
|
|
8
8
|
exports.generate = generate;
|
|
9
9
|
exports.empty = empty;
|
|
10
10
|
exports.TM = TM;
|
|
@@ -23,21 +23,25 @@ const jostraca_1 = require("jostraca");
|
|
|
23
23
|
// placing a same-named file in its own tm folder (passed as spec.tm by
|
|
24
24
|
// the project's build actions). The fragment file's final newline is
|
|
25
25
|
// dropped at load, so slot values fully control trailing bytes.
|
|
26
|
-
// Package tm
|
|
26
|
+
// Package tm root: compiled code lives at <pkg>/dist/env/lambda (three
|
|
27
27
|
// levels down), but test transforms run the source at <pkg>/env/lambda
|
|
28
|
-
// (two levels down) - probe both.
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
28
|
+
// (two levels down) - probe both. Areas (tm/<area>/...) group fragment
|
|
29
|
+
// sets: 'lambda' (the EnvLambda templates) and 'env' (per-environment
|
|
30
|
+
// deployment templates).
|
|
31
|
+
const PKG_TM_ROOT = [
|
|
32
|
+
path_1.default.join(__dirname, '..', '..', '..', 'tm'),
|
|
33
|
+
path_1.default.join(__dirname, '..', '..', 'tm'),
|
|
32
34
|
].find((p) => fs_1.default.existsSync(p)) ||
|
|
33
|
-
path_1.default.join(__dirname, '..', '..', '..', 'tm'
|
|
35
|
+
path_1.default.join(__dirname, '..', '..', '..', 'tm');
|
|
36
|
+
exports.PKG_TM_ROOT = PKG_TM_ROOT;
|
|
37
|
+
const PKG_TM = path_1.default.join(PKG_TM_ROOT, 'lambda');
|
|
34
38
|
exports.PKG_TM = PKG_TM;
|
|
35
|
-
function loadFragment(name, spec) {
|
|
39
|
+
function loadFragment(name, spec, area = 'lambda') {
|
|
36
40
|
const candidates = [];
|
|
37
41
|
if (spec === null || spec === void 0 ? void 0 : spec.tm) {
|
|
38
42
|
candidates.push(path_1.default.join(spec.tm, name));
|
|
39
43
|
}
|
|
40
|
-
candidates.push(path_1.default.join(
|
|
44
|
+
candidates.push(path_1.default.join(PKG_TM_ROOT, area, name));
|
|
41
45
|
for (const c of candidates) {
|
|
42
46
|
if (fs_1.default.existsSync(c)) {
|
|
43
47
|
let src = fs_1.default.readFileSync(c, 'utf8');
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generate.js","sourceRoot":"","sources":["../../../env/lambda/generate.ts"],"names":[],"mappings":";AAAA,oDAAoD;;;;;;
|
|
1
|
+
{"version":3,"file":"generate.js","sourceRoot":"","sources":["../../../env/lambda/generate.ts"],"names":[],"mappings":"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"}
|
package/env/env_gen.ts
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/* Copyright © 2026 Voxgig Ltd, MIT License. */
|
|
2
|
+
|
|
3
|
+
// EnvGen: per-environment deployment artifact generation. The project
|
|
4
|
+
// model declares its target environments under `main: env:`:
|
|
5
|
+
//
|
|
6
|
+
// main: env: local: { active: true }
|
|
7
|
+
// main: env: aws: { active: true, region: 'eu-west-1', stage: 'dev' }
|
|
8
|
+
//
|
|
9
|
+
// For each active environment this generates gen/env/<name>/ from the
|
|
10
|
+
// fragment set tm/env/<kind>/ (kind defaults to the env name, so a
|
|
11
|
+
// project can declare e.g. `aws2: { kind: 'aws', region: ... }`).
|
|
12
|
+
// Fragments are project-shadowable via spec.tm (the project's tm/env
|
|
13
|
+
// folder), like the lambda templates.
|
|
14
|
+
|
|
15
|
+
import Fs from 'fs'
|
|
16
|
+
import Path from 'path'
|
|
17
|
+
|
|
18
|
+
import { camelify } from '@voxgig/util'
|
|
19
|
+
|
|
20
|
+
import { CoreConfShape } from '../shape/conf'
|
|
21
|
+
|
|
22
|
+
import { generate, loadFragment, renderFragment } from './lambda/generate'
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
// The supported environment kinds and the files each generates.
|
|
26
|
+
// `out` may contain $$slots$$ (rendered with the same slot values).
|
|
27
|
+
const ENV_FILES: Record<string, { frag: string, out: string }[]> = {
|
|
28
|
+
local: [
|
|
29
|
+
{ frag: 'local/run.sh.frag', out: 'run.sh' },
|
|
30
|
+
{ frag: 'local/README.md.frag', out: 'README.md' },
|
|
31
|
+
],
|
|
32
|
+
basic: [
|
|
33
|
+
{ frag: 'basic/backend.service.frag', out: '$$name$$-backend.service' },
|
|
34
|
+
{ frag: 'basic/deploy.sh.frag', out: 'deploy.sh' },
|
|
35
|
+
{ frag: 'basic/README.md.frag', out: 'README.md' },
|
|
36
|
+
],
|
|
37
|
+
docker: [
|
|
38
|
+
{ frag: 'docker/Dockerfile.frag', out: 'Dockerfile' },
|
|
39
|
+
{ frag: 'docker/compose.yml.frag', out: 'compose.yml' },
|
|
40
|
+
{ frag: 'docker/README.md.frag', out: 'README.md' },
|
|
41
|
+
],
|
|
42
|
+
vm: [
|
|
43
|
+
{ frag: 'vm/cloud-init.yaml.frag', out: 'cloud-init.yaml' },
|
|
44
|
+
{ frag: 'vm/README.md.frag', out: 'README.md' },
|
|
45
|
+
],
|
|
46
|
+
aws: [
|
|
47
|
+
{ frag: 'aws/serverless.yml.frag', out: 'serverless.yml' },
|
|
48
|
+
{ frag: 'aws/README.md.frag', out: 'README.md' },
|
|
49
|
+
],
|
|
50
|
+
azure: [
|
|
51
|
+
{ frag: 'azure/host.json.frag', out: 'host.json' },
|
|
52
|
+
{ frag: 'azure/README.md.frag', out: 'README.md' },
|
|
53
|
+
],
|
|
54
|
+
cloudflare: [
|
|
55
|
+
{ frag: 'cloudflare/wrangler.toml.frag', out: 'wrangler.toml' },
|
|
56
|
+
{ frag: 'cloudflare/README.md.frag', out: 'README.md' },
|
|
57
|
+
],
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// The runtime entry point + Seneca config setup each environment needs
|
|
61
|
+
// under src/env/. These are generated ONCE (only when missing): after
|
|
62
|
+
// creation the project owns them, like the scaffold's local entry. All
|
|
63
|
+
// entries route Seneca configuration through src/env/shared/basic.ts.
|
|
64
|
+
// `dir` is relative to the project src/env folder.
|
|
65
|
+
const ENV_SRC: Record<string, { frag: string, dir: string, out: string }[]> = {
|
|
66
|
+
local: [], // scaffold provides src/env/local/local.ts
|
|
67
|
+
basic: [
|
|
68
|
+
{ frag: 'server.entry.ts.frag', dir: 'basic', out: 'basic.ts' },
|
|
69
|
+
],
|
|
70
|
+
docker: [
|
|
71
|
+
{ frag: 'server.entry.ts.frag', dir: 'docker', out: 'docker.ts' },
|
|
72
|
+
],
|
|
73
|
+
vm: [
|
|
74
|
+
{ frag: 'server.entry.ts.frag', dir: 'vm', out: 'vm.ts' },
|
|
75
|
+
],
|
|
76
|
+
aws: [
|
|
77
|
+
// The Lambda bootstrap the generated handlers import.
|
|
78
|
+
{ frag: 'aws/lambda.entry.ts.frag', dir: 'lambda', out: 'lambda.ts' },
|
|
79
|
+
],
|
|
80
|
+
azure: [
|
|
81
|
+
{ frag: 'azure/entry.ts.frag', dir: 'azure', out: 'azure.ts' },
|
|
82
|
+
],
|
|
83
|
+
cloudflare: [
|
|
84
|
+
{ frag: 'cloudflare/worker.ts.frag', dir: 'cloudflare', out: 'worker.ts' },
|
|
85
|
+
],
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const KINDS = Object.keys(ENV_FILES)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
const env_gen = async (model: any, spec: {
|
|
92
|
+
folder: string // gen/env output root; each env writes <folder>/<name>/
|
|
93
|
+
tm?: string // project tm/env folder (fragment shadowing)
|
|
94
|
+
src?: string // project src/env folder: runtime entries generated
|
|
95
|
+
// here ONCE (existing files are never overwritten)
|
|
96
|
+
}) => {
|
|
97
|
+
|
|
98
|
+
const core = CoreConfShape(model.main.conf.core)
|
|
99
|
+
const envs = model.main.env || {}
|
|
100
|
+
|
|
101
|
+
for (const entry of Object.entries(envs)) {
|
|
102
|
+
const name = entry[0]
|
|
103
|
+
const def: any = entry[1] || {}
|
|
104
|
+
|
|
105
|
+
if (false === def.active) {
|
|
106
|
+
continue
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const kind = def.kind || name
|
|
110
|
+
const filedefs = ENV_FILES[kind]
|
|
111
|
+
if (null == filedefs) {
|
|
112
|
+
throw new Error('@voxgig/build: unknown environment kind: ' + kind +
|
|
113
|
+
' (env ' + name + '; known kinds: ' + KINDS.join(', ') + ')')
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Slot values: project identity, ports, and per-env config with
|
|
117
|
+
// sensible defaults. Additional scalar config keys on the env
|
|
118
|
+
// definition become slots too.
|
|
119
|
+
const slots: Record<string, any> = {
|
|
120
|
+
name: core.name,
|
|
121
|
+
Name: camelify(core.name),
|
|
122
|
+
env: name,
|
|
123
|
+
port: model.main.conf.port?.backend || 8080,
|
|
124
|
+
node: def.node || '22',
|
|
125
|
+
region: def.region || 'us-east-1',
|
|
126
|
+
stage: def.stage || 'dev',
|
|
127
|
+
}
|
|
128
|
+
for (const key of Object.keys(def)) {
|
|
129
|
+
if ('object' !== typeof def[key] && null == slots[key]) {
|
|
130
|
+
slots[key] = def[key]
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Slot for the env name inside entry fragments.
|
|
135
|
+
slots.env = name
|
|
136
|
+
|
|
137
|
+
const files = filedefs.map((fd) => ({
|
|
138
|
+
name: renderFragment(fd.out, slots),
|
|
139
|
+
content: renderFragment(loadFragment(fd.frag, spec, 'env'), slots),
|
|
140
|
+
}))
|
|
141
|
+
|
|
142
|
+
await generate(spec.folder + '/' + name, files)
|
|
143
|
+
|
|
144
|
+
// Runtime entries: create-once into src/env (user-owned afterwards).
|
|
145
|
+
if (null != spec.src) {
|
|
146
|
+
for (const sd of (ENV_SRC[kind] || [])) {
|
|
147
|
+
const dest = Path.join(spec.src, sd.dir, renderFragment(sd.out, slots))
|
|
148
|
+
if (!Fs.existsSync(dest)) {
|
|
149
|
+
const content =
|
|
150
|
+
renderFragment(loadFragment(sd.frag, spec, 'env'), slots)
|
|
151
|
+
Fs.mkdirSync(Path.dirname(dest), { recursive: true })
|
|
152
|
+
Fs.writeFileSync(dest, content)
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
export {
|
|
161
|
+
env_gen,
|
|
162
|
+
ENV_FILES,
|
|
163
|
+
ENV_SRC,
|
|
164
|
+
KINDS,
|
|
165
|
+
}
|
package/env/lambda/generate.ts
CHANGED
|
@@ -17,22 +17,28 @@ import { Jostraca, Project, File, Content } from 'jostraca'
|
|
|
17
17
|
// the project's build actions). The fragment file's final newline is
|
|
18
18
|
// dropped at load, so slot values fully control trailing bytes.
|
|
19
19
|
|
|
20
|
-
// Package tm
|
|
20
|
+
// Package tm root: compiled code lives at <pkg>/dist/env/lambda (three
|
|
21
21
|
// levels down), but test transforms run the source at <pkg>/env/lambda
|
|
22
|
-
// (two levels down) - probe both.
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
// (two levels down) - probe both. Areas (tm/<area>/...) group fragment
|
|
23
|
+
// sets: 'lambda' (the EnvLambda templates) and 'env' (per-environment
|
|
24
|
+
// deployment templates).
|
|
25
|
+
const PKG_TM_ROOT = [
|
|
26
|
+
Path.join(__dirname, '..', '..', '..', 'tm'),
|
|
27
|
+
Path.join(__dirname, '..', '..', 'tm'),
|
|
26
28
|
].find((p) => Fs.existsSync(p)) ||
|
|
27
|
-
Path.join(__dirname, '..', '..', '..', 'tm'
|
|
29
|
+
Path.join(__dirname, '..', '..', '..', 'tm')
|
|
28
30
|
|
|
31
|
+
const PKG_TM = Path.join(PKG_TM_ROOT, 'lambda')
|
|
29
32
|
|
|
30
|
-
|
|
33
|
+
|
|
34
|
+
function loadFragment(
|
|
35
|
+
name: string, spec?: { tm?: string }, area: string = 'lambda'
|
|
36
|
+
): string {
|
|
31
37
|
const candidates = []
|
|
32
38
|
if (spec?.tm) {
|
|
33
39
|
candidates.push(Path.join(spec.tm, name))
|
|
34
40
|
}
|
|
35
|
-
candidates.push(Path.join(
|
|
41
|
+
candidates.push(Path.join(PKG_TM_ROOT, area, name))
|
|
36
42
|
|
|
37
43
|
for (const c of candidates) {
|
|
38
44
|
if (Fs.existsSync(c)) {
|
|
@@ -102,4 +108,5 @@ export {
|
|
|
102
108
|
renderFragment,
|
|
103
109
|
listFragments,
|
|
104
110
|
PKG_TM,
|
|
111
|
+
PKG_TM_ROOT,
|
|
105
112
|
}
|
package/package.json
CHANGED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# $$Name$$ - aws environment (Serverless Framework)
|
|
2
|
+
|
|
3
|
+
cd backend && npm run build
|
|
4
|
+
cd gen/env/aws && npx serverless deploy --stage $$stage$$
|
|
5
|
+
|
|
6
|
+
serverless.yml composes the generated srv.yml (functions) and res.yml
|
|
7
|
+
(resources); both are regenerated by model-build. Provider settings
|
|
8
|
+
(region/stage/runtime) come from the model env entry - change them with
|
|
9
|
+
the model, or eject env/aws/serverless.yml.frag for structural edits.
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
|
|
2
|
+
// $$Name$$ backend - AWS Lambda environment bootstrap (generated once;
|
|
3
|
+
// this file is yours to edit). The generated handlers
|
|
4
|
+
// (src/handler/lambda/<srv>.ts) call getSeneca() for a ready Seneca
|
|
5
|
+
// instance wired for the Lambda + API Gateway path, configured by the
|
|
6
|
+
// shared setup (src/env/shared/basic.ts).
|
|
7
|
+
|
|
8
|
+
import Seneca from 'seneca'
|
|
9
|
+
import { Live } from '@voxgig/system'
|
|
10
|
+
|
|
11
|
+
import { basic, base } from '../shared/basic'
|
|
12
|
+
|
|
13
|
+
import Pkg from '../../../package.json'
|
|
14
|
+
import Model from '../../../model/model.json'
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
const STAGE = process.env.STAGE || 'local'
|
|
18
|
+
|
|
19
|
+
const Main = Model.main as any
|
|
20
|
+
|
|
21
|
+
let seneca: any = null
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
async function getSeneca(srvname: string, complete: Function): Promise<any> {
|
|
25
|
+
const { deep } = Seneca.util
|
|
26
|
+
|
|
27
|
+
if (null == seneca) {
|
|
28
|
+
const srv = Main.srv[srvname]
|
|
29
|
+
|
|
30
|
+
seneca = Seneca(deep(base.seneca, {
|
|
31
|
+
tag: srvname + '-$$name$$-' + STAGE + '@' + Pkg.version,
|
|
32
|
+
timeout: srv.env.lambda.timeout * 60 * 1000,
|
|
33
|
+
})).test()
|
|
34
|
+
|
|
35
|
+
seneca.context.model = Model
|
|
36
|
+
seneca.context.srvname = srvname
|
|
37
|
+
seneca.context.stage = STAGE
|
|
38
|
+
seneca.context.env = 'lambda'
|
|
39
|
+
seneca.context.pkg = Pkg
|
|
40
|
+
|
|
41
|
+
basic(seneca, { reload: { active: false } })
|
|
42
|
+
|
|
43
|
+
seneca
|
|
44
|
+
.use('gateway')
|
|
45
|
+
.use('gateway-lambda', {
|
|
46
|
+
auth: {
|
|
47
|
+
token: {
|
|
48
|
+
name: '$$name$$-auth',
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
})
|
|
52
|
+
.use('gateway-auth', {
|
|
53
|
+
spec: {
|
|
54
|
+
lambda_cookie: {
|
|
55
|
+
active: true,
|
|
56
|
+
token: {
|
|
57
|
+
name: '$$name$$-auth',
|
|
58
|
+
},
|
|
59
|
+
user: {
|
|
60
|
+
auth: true,
|
|
61
|
+
require: srv.user?.required ?? true,
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
seneca.use(Live, {
|
|
68
|
+
srv: {
|
|
69
|
+
name: srvname,
|
|
70
|
+
folder: __dirname + '/../../srv',
|
|
71
|
+
},
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
// Call ready() exactly once - a second ready() on an already-ready
|
|
75
|
+
// instance never resolves under seneca 4.0.0-rc4.
|
|
76
|
+
await seneca.ready()
|
|
77
|
+
|
|
78
|
+
if (complete) {
|
|
79
|
+
await complete(seneca)
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return seneca
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
export {
|
|
88
|
+
getSeneca,
|
|
89
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# $$Name$$ backend - Serverless Framework config (generated; customize via
|
|
2
|
+
# tm/env/aws/). Composes the model-generated pieces: per-service function
|
|
3
|
+
# definitions (srv.yml) and resources (res.yml) live beside this file and
|
|
4
|
+
# are regenerated by model-build - edit the model, not those files.
|
|
5
|
+
service: $$name$$-backend
|
|
6
|
+
|
|
7
|
+
frameworkVersion: '4'
|
|
8
|
+
|
|
9
|
+
provider:
|
|
10
|
+
name: aws
|
|
11
|
+
runtime: nodejs$$node$$.x
|
|
12
|
+
region: $$region$$
|
|
13
|
+
stage: ${opt:stage, '$$stage$$'}
|
|
14
|
+
|
|
15
|
+
custom:
|
|
16
|
+
# res.yml derives the IAM role name suffix from this index.
|
|
17
|
+
index:
|
|
18
|
+
BasicLambdaRole: '01'
|
|
19
|
+
|
|
20
|
+
functions: ${file(./srv.yml)}
|
|
21
|
+
|
|
22
|
+
resources:
|
|
23
|
+
Resources: ${file(./res.yml)}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# $$Name$$ - azure environment (starting point)
|
|
2
|
+
|
|
3
|
+
Azure Functions host config for the $$Name$$ backend. This is a starting
|
|
4
|
+
point: Azure needs one function.json binding per exposed function - derive
|
|
5
|
+
them from the service definitions the model generates for aws
|
|
6
|
+
(gen/env/aws/srv.yml lists every handler and route). Eject
|
|
7
|
+
env/azure/host.json.frag to customize.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
|
|
2
|
+
// $$Name$$ backend - azure environment entry (generated once; this file is
|
|
3
|
+
// yours to edit). STARTING POINT: an Azure Functions HTTP handler shape -
|
|
4
|
+
// wire the Seneca gateway the way src/env/lambda/lambda.ts does for AWS.
|
|
5
|
+
|
|
6
|
+
import Seneca from 'seneca'
|
|
7
|
+
|
|
8
|
+
import { basic, base } from '../shared/basic'
|
|
9
|
+
|
|
10
|
+
import Pkg from '../../../package.json'
|
|
11
|
+
import Model from '../../../model/model.json'
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
let seneca: any = null
|
|
15
|
+
|
|
16
|
+
async function getSeneca(): Promise<any> {
|
|
17
|
+
if (null == seneca) {
|
|
18
|
+
seneca = Seneca(Seneca.util.deep(base.seneca, {
|
|
19
|
+
tag: '$$name$$-azure@' + Pkg.version,
|
|
20
|
+
})).test()
|
|
21
|
+
|
|
22
|
+
seneca.context.model = Model
|
|
23
|
+
seneca.context.env = 'azure'
|
|
24
|
+
seneca.context.pkg = Pkg
|
|
25
|
+
|
|
26
|
+
basic(seneca, { reload: { active: false } })
|
|
27
|
+
|
|
28
|
+
await seneca.ready()
|
|
29
|
+
}
|
|
30
|
+
return seneca
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
// Azure Functions v4 programming model handler (add @azure/functions and
|
|
35
|
+
// register per-route handlers derived from the model - see
|
|
36
|
+
// gen/env/aws/srv.yml for the full route list).
|
|
37
|
+
async function handler(request: any, _context: any) {
|
|
38
|
+
await getSeneca()
|
|
39
|
+
return {
|
|
40
|
+
status: 501,
|
|
41
|
+
jsonBody: { ok: false, why: '$$name$$-backend azure entry not yet wired' },
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
export {
|
|
47
|
+
getSeneca,
|
|
48
|
+
handler,
|
|
49
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# systemd unit for the $$Name$$ backend (generated; customize via tm/env/basic/)
|
|
2
|
+
[Unit]
|
|
3
|
+
Description=$$Name$$ backend
|
|
4
|
+
After=network.target
|
|
5
|
+
|
|
6
|
+
[Service]
|
|
7
|
+
WorkingDirectory=/opt/$$name$$/backend
|
|
8
|
+
ExecStart=/usr/bin/node dist/env/local/local.js
|
|
9
|
+
Restart=always
|
|
10
|
+
Environment=NODE_ENV=production
|
|
11
|
+
Environment=PORT=$$port$$
|
|
12
|
+
|
|
13
|
+
[Install]
|
|
14
|
+
WantedBy=multi-user.target
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# Deploy the $$Name$$ backend to a plain remote server (generated).
|
|
3
|
+
# Usage: BASIC_HOST=user@host sh deploy.sh
|
|
4
|
+
set -e
|
|
5
|
+
: "${BASIC_HOST:?set BASIC_HOST=user@host}"
|
|
6
|
+
cd "$(dirname "$0")/../../.."
|
|
7
|
+
npm run build
|
|
8
|
+
rsync -az --delete \
|
|
9
|
+
--exclude node_modules --exclude gen --exclude dist-test \
|
|
10
|
+
./ "$BASIC_HOST:/opt/$$name$$/backend/"
|
|
11
|
+
ssh "$BASIC_HOST" "cd /opt/$$name$$/backend && npm install --omit=dev \
|
|
12
|
+
&& sudo cp gen/env/basic/$$name$$-backend.service /etc/systemd/system/ \
|
|
13
|
+
&& sudo systemctl daemon-reload && sudo systemctl restart $$name$$-backend"
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# $$Name$$ - cloudflare environment (starting point)
|
|
2
|
+
|
|
3
|
+
cd gen/env/cloudflare && npx wrangler deploy
|
|
4
|
+
|
|
5
|
+
The worker entry (src/env/cloudflare/worker.ts - yours to edit, wrangler
|
|
6
|
+
bundles the TS directly) is a 501 stub: wire the Seneca gateway into the
|
|
7
|
+
fetch handler (src/env/lambda/lambda.ts is the AWS model to follow).
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// $$Name$$ backend - Cloudflare Worker entry (generated once; this file is
|
|
2
|
+
// yours to edit). STARTING POINT: Workers use a fetch handler rather than
|
|
3
|
+
// a long-lived process - adapt the gateway wiring (src/env/lambda/lambda.ts
|
|
4
|
+
// is the AWS equivalent) to route requests into Seneca here.
|
|
5
|
+
|
|
6
|
+
export default {
|
|
7
|
+
async fetch(_request: Request, _env: unknown, _ctx: unknown) {
|
|
8
|
+
return new Response(JSON.stringify({
|
|
9
|
+
ok: false,
|
|
10
|
+
why: '$$name$$-backend worker entry not yet wired',
|
|
11
|
+
}), { status: 501, headers: { 'content-type': 'application/json' } })
|
|
12
|
+
},
|
|
13
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# $$Name$$ backend image (generated; customize via tm/env/docker/)
|
|
2
|
+
FROM node:$$node$$-slim
|
|
3
|
+
WORKDIR /app
|
|
4
|
+
COPY package*.json ./
|
|
5
|
+
RUN npm ci --omit=dev
|
|
6
|
+
COPY model/model.json model/model.json
|
|
7
|
+
COPY dist/ dist/
|
|
8
|
+
ENV NODE_ENV=production
|
|
9
|
+
EXPOSE $$port$$
|
|
10
|
+
CMD ["node", "dist/env/local/local.js"]
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
|
|
2
|
+
// $$Name$$ backend - $$env$$ environment entry (generated once; this file
|
|
3
|
+
// is yours to edit). A long-lived server process using the shared Seneca
|
|
4
|
+
// setup (src/env/shared/basic.ts); reads its env config from the model
|
|
5
|
+
// (main.env.$$env$$).
|
|
6
|
+
|
|
7
|
+
import Seneca from 'seneca'
|
|
8
|
+
import { Local } from '@voxgig/system'
|
|
9
|
+
|
|
10
|
+
import { basic, base } from '../shared/basic'
|
|
11
|
+
|
|
12
|
+
import Pkg from '../../../package.json'
|
|
13
|
+
import Model from '../../../model/model.json'
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
run()
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
async function run() {
|
|
20
|
+
const { deep } = Seneca.util
|
|
21
|
+
|
|
22
|
+
const envdef: any = (Model as any).main.env?.['$$env$$'] || {}
|
|
23
|
+
|
|
24
|
+
const seneca = Seneca(deep(base.seneca, {
|
|
25
|
+
tag: '$$name$$-$$env$$',
|
|
26
|
+
log: { level: envdef.log || 'warn' },
|
|
27
|
+
}, envdef.seneca || {}))
|
|
28
|
+
|
|
29
|
+
seneca.context.model = Model
|
|
30
|
+
seneca.context.env = '$$env$$'
|
|
31
|
+
seneca.context.stage = process.env.STAGE || '$$env$$'
|
|
32
|
+
seneca.context.srvname = 'all'
|
|
33
|
+
seneca.context.pkg = Pkg
|
|
34
|
+
|
|
35
|
+
basic(seneca)
|
|
36
|
+
|
|
37
|
+
seneca.use(Local, {
|
|
38
|
+
srv: {
|
|
39
|
+
folder: __dirname + '/../../../dist/srv',
|
|
40
|
+
},
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
await seneca.ready()
|
|
44
|
+
|
|
45
|
+
console.log('$$name$$-backend started', {
|
|
46
|
+
env: '$$env$$', version: Pkg.version, port: $$port$$,
|
|
47
|
+
})
|
|
48
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#cloud-config
|
|
2
|
+
# $$Name$$ backend VM bootstrap (generated; customize via tm/env/vm/)
|
|
3
|
+
packages:
|
|
4
|
+
- nodejs
|
|
5
|
+
- npm
|
|
6
|
+
- rsync
|
|
7
|
+
write_files:
|
|
8
|
+
- path: /etc/systemd/system/$$name$$-backend.service
|
|
9
|
+
content: |
|
|
10
|
+
[Unit]
|
|
11
|
+
Description=$$Name$$ backend
|
|
12
|
+
After=network.target
|
|
13
|
+
[Service]
|
|
14
|
+
WorkingDirectory=/opt/$$name$$/backend
|
|
15
|
+
ExecStart=/usr/bin/node dist/env/local/local.js
|
|
16
|
+
Restart=always
|
|
17
|
+
Environment=NODE_ENV=production
|
|
18
|
+
[Install]
|
|
19
|
+
WantedBy=multi-user.target
|
|
20
|
+
runcmd:
|
|
21
|
+
- mkdir -p /opt/$$name$$/backend
|
|
22
|
+
- systemctl daemon-reload
|
|
23
|
+
- systemctl enable $$name$$-backend
|