@voxgig/build 4.1.0 → 4.3.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.
Files changed (56) hide show
  1. package/build.ts +23 -0
  2. package/dist/build.d.ts +34 -1
  3. package/dist/build.js +19 -1
  4. package/dist/build.js.map +1 -1
  5. package/dist/env/env_gen.d.ts +17 -0
  6. package/dist/env/env_gen.js +154 -0
  7. package/dist/env/env_gen.js.map +1 -0
  8. package/dist/env/lambda/generate.d.ts +3 -2
  9. package/dist/env/lambda/generate.js +13 -9
  10. package/dist/env/lambda/generate.js.map +1 -1
  11. package/dist/env/web/web_gen.d.ts +19 -0
  12. package/dist/env/web/web_gen.js +93 -0
  13. package/dist/env/web/web_gen.js.map +1 -0
  14. package/env/env_gen.ts +179 -0
  15. package/env/lambda/generate.ts +15 -8
  16. package/env/web/web_gen.ts +115 -0
  17. package/package.json +1 -1
  18. package/tm/env/aws/README.md.frag +9 -0
  19. package/tm/env/aws/lambda.entry.ts.frag +89 -0
  20. package/tm/env/aws/serverless.yml.frag +23 -0
  21. package/tm/env/azure/README.md.frag +7 -0
  22. package/tm/env/azure/entry.ts.frag +49 -0
  23. package/tm/env/azure/host.json.frag +8 -0
  24. package/tm/env/basic/README.md.frag +7 -0
  25. package/tm/env/basic/backend.service.frag +14 -0
  26. package/tm/env/basic/deploy.sh.frag +13 -0
  27. package/tm/env/cloudflare/README.md.frag +7 -0
  28. package/tm/env/cloudflare/worker.ts.frag +13 -0
  29. package/tm/env/cloudflare/wrangler.toml.frag +7 -0
  30. package/tm/env/docker/Dockerfile.frag +10 -0
  31. package/tm/env/docker/README.md.frag +7 -0
  32. package/tm/env/docker/compose.yml.frag +10 -0
  33. package/tm/env/local/README.md.frag +7 -0
  34. package/tm/env/local/run.sh.frag +6 -0
  35. package/tm/env/server.entry.ts.frag +48 -0
  36. package/tm/env/vm/README.md.frag +5 -0
  37. package/tm/env/vm/cloud-init.yaml.frag +23 -0
  38. package/tm/web/backend/env/web/web.ts.frag +120 -0
  39. package/tm/web/backend/srv/auth/auth-srv.ts.frag +4 -0
  40. package/tm/web/backend/srv/auth/get_info.ts.frag +5 -0
  41. package/tm/web/backend/srv/auth/load_auth.ts.frag +6 -0
  42. package/tm/web/backend/srv/auth/signin_user.ts.frag +14 -0
  43. package/tm/web/backend/srv/auth/signout_user.ts.frag +5 -0
  44. package/tm/web/backend/srv/auth/web_load_auth.ts.frag +12 -0
  45. package/tm/web/backend/srv/auth/web_signin_user.ts.frag +25 -0
  46. package/tm/web/backend/srv/auth/web_signout_user.ts.frag +9 -0
  47. package/tm/web/web/e2e/smoke.spec.js.frag +21 -0
  48. package/tm/web/web/index.html.frag +13 -0
  49. package/tm/web/web/package.json.frag +17 -0
  50. package/tm/web/web/playwright.config.js.frag +36 -0
  51. package/tm/web/web/src/bus.js.frag +61 -0
  52. package/tm/web/web/src/cmp/admin.js.frag +254 -0
  53. package/tm/web/web/src/cmp/app.js.frag +30 -0
  54. package/tm/web/web/src/cmp/auth.js.frag +97 -0
  55. package/tm/web/web/src/main.js.frag +10 -0
  56. package/tm/web/web/src/style.css.frag +26 -0
package/env/env_gen.ts ADDED
@@ -0,0 +1,179 @@
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
+ import { web_gen } from './web/web_gen'
24
+
25
+
26
+ // The supported environment kinds and the files each generates.
27
+ // `out` may contain $$slots$$ (rendered with the same slot values).
28
+ const ENV_FILES: Record<string, { frag: string, out: string }[]> = {
29
+ local: [
30
+ { frag: 'local/run.sh.frag', out: 'run.sh' },
31
+ { frag: 'local/README.md.frag', out: 'README.md' },
32
+ ],
33
+ basic: [
34
+ { frag: 'basic/backend.service.frag', out: '$$name$$-backend.service' },
35
+ { frag: 'basic/deploy.sh.frag', out: 'deploy.sh' },
36
+ { frag: 'basic/README.md.frag', out: 'README.md' },
37
+ ],
38
+ docker: [
39
+ { frag: 'docker/Dockerfile.frag', out: 'Dockerfile' },
40
+ { frag: 'docker/compose.yml.frag', out: 'compose.yml' },
41
+ { frag: 'docker/README.md.frag', out: 'README.md' },
42
+ ],
43
+ vm: [
44
+ { frag: 'vm/cloud-init.yaml.frag', out: 'cloud-init.yaml' },
45
+ { frag: 'vm/README.md.frag', out: 'README.md' },
46
+ ],
47
+ aws: [
48
+ { frag: 'aws/serverless.yml.frag', out: 'serverless.yml' },
49
+ { frag: 'aws/README.md.frag', out: 'README.md' },
50
+ ],
51
+ azure: [
52
+ { frag: 'azure/host.json.frag', out: 'host.json' },
53
+ { frag: 'azure/README.md.frag', out: 'README.md' },
54
+ ],
55
+ cloudflare: [
56
+ { frag: 'cloudflare/wrangler.toml.frag', out: 'wrangler.toml' },
57
+ { frag: 'cloudflare/README.md.frag', out: 'README.md' },
58
+ ],
59
+ }
60
+
61
+ // The runtime entry point + Seneca config setup each environment needs
62
+ // under src/env/. These are generated ONCE (only when missing): after
63
+ // creation the project owns them, like the scaffold's local entry. All
64
+ // entries route Seneca configuration through src/env/shared/basic.ts.
65
+ // `dir` is relative to the project src/env folder.
66
+ const ENV_SRC: Record<string, { frag: string, dir: string, out: string }[]> = {
67
+ local: [], // scaffold provides src/env/local/local.ts
68
+ basic: [
69
+ { frag: 'server.entry.ts.frag', dir: 'basic', out: 'basic.ts' },
70
+ ],
71
+ docker: [
72
+ { frag: 'server.entry.ts.frag', dir: 'docker', out: 'docker.ts' },
73
+ ],
74
+ vm: [
75
+ { frag: 'server.entry.ts.frag', dir: 'vm', out: 'vm.ts' },
76
+ ],
77
+ aws: [
78
+ // The Lambda bootstrap the generated handlers import.
79
+ { frag: 'aws/lambda.entry.ts.frag', dir: 'lambda', out: 'lambda.ts' },
80
+ ],
81
+ azure: [
82
+ { frag: 'azure/entry.ts.frag', dir: 'azure', out: 'azure.ts' },
83
+ ],
84
+ cloudflare: [
85
+ { frag: 'cloudflare/worker.ts.frag', dir: 'cloudflare', out: 'worker.ts' },
86
+ ],
87
+ }
88
+
89
+ const KINDS = Object.keys(ENV_FILES)
90
+
91
+
92
+ const env_gen = async (model: any, spec: {
93
+ folder: string // gen/env output root; each env writes <folder>/<name>/
94
+ tm?: string // project tm/env folder (fragment shadowing)
95
+ src?: string // project src/env folder: runtime entries generated
96
+ // here ONCE (existing files are never overwritten)
97
+ root?: string // project root (holds backend/ and web/) - required
98
+ // for the 'web' kind (full frontend generation)
99
+ }) => {
100
+
101
+ const core = CoreConfShape(model.main.conf.core)
102
+ const envs = model.main.env || {}
103
+
104
+ for (const entry of Object.entries(envs)) {
105
+ const name = entry[0]
106
+ const def: any = entry[1] || {}
107
+
108
+ if (false === def.active) {
109
+ continue
110
+ }
111
+
112
+ const kind = def.kind || name
113
+
114
+ // The 'web' kind is the frontend SPA (+ backend web runner + auth),
115
+ // not a flat deployment-artifact set - delegate to EnvWeb.
116
+ if ('web' === kind) {
117
+ if (null == spec.root) {
118
+ throw new Error('@voxgig/build: env kind "web" needs spec.root ' +
119
+ '(the project root); pass it from the build action')
120
+ }
121
+ await web_gen(model, { root: spec.root, tm: spec.tm, env: def })
122
+ continue
123
+ }
124
+ const filedefs = ENV_FILES[kind]
125
+ if (null == filedefs) {
126
+ throw new Error('@voxgig/build: unknown environment kind: ' + kind +
127
+ ' (env ' + name + '; known kinds: ' + KINDS.join(', ') + ')')
128
+ }
129
+
130
+ // Slot values: project identity, ports, and per-env config with
131
+ // sensible defaults. Additional scalar config keys on the env
132
+ // definition become slots too.
133
+ const slots: Record<string, any> = {
134
+ name: core.name,
135
+ Name: camelify(core.name),
136
+ env: name,
137
+ port: model.main.conf.port?.backend || 8080,
138
+ node: def.node || '22',
139
+ region: def.region || 'us-east-1',
140
+ stage: def.stage || 'dev',
141
+ }
142
+ for (const key of Object.keys(def)) {
143
+ if ('object' !== typeof def[key] && null == slots[key]) {
144
+ slots[key] = def[key]
145
+ }
146
+ }
147
+
148
+ // Slot for the env name inside entry fragments.
149
+ slots.env = name
150
+
151
+ const files = filedefs.map((fd) => ({
152
+ name: renderFragment(fd.out, slots),
153
+ content: renderFragment(loadFragment(fd.frag, spec, 'env'), slots),
154
+ }))
155
+
156
+ await generate(spec.folder + '/' + name, files)
157
+
158
+ // Runtime entries: create-once into src/env (user-owned afterwards).
159
+ if (null != spec.src) {
160
+ for (const sd of (ENV_SRC[kind] || [])) {
161
+ const dest = Path.join(spec.src, sd.dir, renderFragment(sd.out, slots))
162
+ if (!Fs.existsSync(dest)) {
163
+ const content =
164
+ renderFragment(loadFragment(sd.frag, spec, 'env'), slots)
165
+ Fs.mkdirSync(Path.dirname(dest), { recursive: true })
166
+ Fs.writeFileSync(dest, content)
167
+ }
168
+ }
169
+ }
170
+ }
171
+ }
172
+
173
+
174
+ export {
175
+ env_gen,
176
+ ENV_FILES,
177
+ ENV_SRC,
178
+ KINDS,
179
+ }
@@ -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 folder: compiled code lives at <pkg>/dist/env/lambda (three
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
- const PKG_TM = [
24
- Path.join(__dirname, '..', '..', '..', 'tm', 'lambda'),
25
- Path.join(__dirname, '..', '..', 'tm', 'lambda'),
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', 'lambda')
29
+ Path.join(__dirname, '..', '..', '..', 'tm')
28
30
 
31
+ const PKG_TM = Path.join(PKG_TM_ROOT, 'lambda')
29
32
 
30
- function loadFragment(name: string, spec?: { tm?: string }): string {
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(PKG_TM, name))
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
  }
@@ -0,0 +1,115 @@
1
+ /* Copyright © 2026 Voxgig Ltd, MIT License. */
2
+
3
+ // EnvWeb: generate the experimental web frontend - a Single Page App built
4
+ // from web components on a client-side Seneca service bus (no React/Vue) -
5
+ // plus the backend web runner and auth service it needs.
6
+ //
7
+ // Everything EnvWeb generates is CREATE-ONCE: a project owns its frontend
8
+ // after generation (the SPA is a starting point users heavily customize).
9
+ // Fragments live in @voxgig/build tm/web/ and are project-shadowable via
10
+ // spec.tm (the project's tm/web folder), like every other template.
11
+ //
12
+ // The entity admin is model-driven at RUNTIME (it fetches the compiled
13
+ // model.json), so the generated SPA adapts to any model with no
14
+ // per-entity code generation.
15
+
16
+ import Fs from 'fs'
17
+ import Path from 'path'
18
+
19
+ import { camelify } from '@voxgig/util'
20
+
21
+ import { CoreConfShape } from '../../shape/conf'
22
+
23
+ import { loadFragment, renderFragment } from '../lambda/generate'
24
+
25
+
26
+ // File manifest: fragment (relative to tm/web) -> output path (relative to
27
+ // the project root, which holds backend/ and web/). $$slots$$ in the
28
+ // output path are rendered too.
29
+ const WEB_FILES: { frag: string, out: string }[] = [
30
+ // Frontend SPA (web/)
31
+ { frag: 'web/package.json.frag', out: 'web/package.json' },
32
+ { frag: 'web/index.html.frag', out: 'web/index.html' },
33
+ { frag: 'web/playwright.config.js.frag', out: 'web/playwright.config.js' },
34
+ { frag: 'web/src/main.js.frag', out: 'web/src/main.js' },
35
+ { frag: 'web/src/bus.js.frag', out: 'web/src/bus.js' },
36
+ { frag: 'web/src/style.css.frag', out: 'web/src/style.css' },
37
+ { frag: 'web/src/cmp/app.js.frag', out: 'web/src/cmp/app.js' },
38
+ { frag: 'web/src/cmp/auth.js.frag', out: 'web/src/cmp/auth.js' },
39
+ { frag: 'web/src/cmp/admin.js.frag', out: 'web/src/cmp/admin.js' },
40
+ { frag: 'web/e2e/smoke.spec.js.frag', out: 'web/e2e/smoke.spec.js' },
41
+
42
+ // Backend web runner + auth service (backend/src/...)
43
+ { frag: 'backend/env/web/web.ts.frag', out: 'backend/src/env/web/web.ts' },
44
+ { frag: 'backend/srv/auth/auth-srv.ts.frag', out: 'backend/src/srv/auth/auth-srv.ts' },
45
+ { frag: 'backend/srv/auth/get_info.ts.frag', out: 'backend/src/srv/auth/get_info.ts' },
46
+ { frag: 'backend/srv/auth/signin_user.ts.frag', out: 'backend/src/srv/auth/signin_user.ts' },
47
+ { frag: 'backend/srv/auth/signout_user.ts.frag', out: 'backend/src/srv/auth/signout_user.ts' },
48
+ { frag: 'backend/srv/auth/load_auth.ts.frag', out: 'backend/src/srv/auth/load_auth.ts' },
49
+ { frag: 'backend/srv/auth/web_signin_user.ts.frag', out: 'backend/src/srv/auth/web_signin_user.ts' },
50
+ { frag: 'backend/srv/auth/web_signout_user.ts.frag', out: 'backend/src/srv/auth/web_signout_user.ts' },
51
+ { frag: 'backend/srv/auth/web_load_auth.ts.frag', out: 'backend/src/srv/auth/web_load_auth.ts' },
52
+ ]
53
+
54
+
55
+ // Default seeded users (local testing only), rendered into the runner.
56
+ const DEFAULT_USERS = [
57
+ { name: 'Alice Example', email: 'alice@example.com', password: 'alice-pass-01' },
58
+ { name: 'Bob Example', email: 'bob@example.com', password: 'bob-pass-01' },
59
+ ]
60
+
61
+
62
+ // web_gen(model, spec): generate the frontend + backend web pieces into
63
+ // the project. spec.root is the project root (parent of backend/); files
64
+ // that already exist are left untouched (create-once).
65
+ const web_gen = async (model: any, spec: {
66
+ root: string // project root (holds backend/ and web/)
67
+ tm?: string // project tm/web folder (fragment shadowing)
68
+ env?: any // the model env entry (config: port, users)
69
+ force?: boolean // overwrite existing files (default: create-once)
70
+ }) => {
71
+
72
+ const core = CoreConfShape(model.main.conf.core)
73
+ const envdef = spec.env || {}
74
+
75
+ const users = envdef.users || DEFAULT_USERS
76
+ const seed = users[0] || DEFAULT_USERS[0]
77
+ const port = envdef.port ||
78
+ (model.main.conf.port && model.main.conf.port.backend) || 8080
79
+
80
+ const slots: Record<string, any> = {
81
+ name: core.name,
82
+ Name: camelify(core.name),
83
+ users: JSON.stringify(users, null, 2),
84
+ e2eport: envdef.e2eport || (port + 10),
85
+ seedEmail: seed.email,
86
+ seedPassword: seed.password,
87
+ }
88
+
89
+ const created: string[] = []
90
+ const skipped: string[] = []
91
+
92
+ for (const fd of WEB_FILES) {
93
+ const outrel = renderFragment(fd.out, slots)
94
+ const dest = Path.join(spec.root, outrel)
95
+
96
+ if (Fs.existsSync(dest) && !spec.force) {
97
+ skipped.push(outrel)
98
+ continue
99
+ }
100
+
101
+ const content = renderFragment(loadFragment(fd.frag, spec, 'web'), slots)
102
+ Fs.mkdirSync(Path.dirname(dest), { recursive: true })
103
+ Fs.writeFileSync(dest, content)
104
+ created.push(outrel)
105
+ }
106
+
107
+ return { created: created.sort(), skipped: skipped.sort() }
108
+ }
109
+
110
+
111
+ export {
112
+ web_gen,
113
+ WEB_FILES,
114
+ DEFAULT_USERS,
115
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voxgig/build",
3
- "version": "4.1.0",
3
+ "version": "4.3.0",
4
4
  "main": "dist/build.js",
5
5
  "type": "commonjs",
6
6
  "types": "dist/build.d.ts",
@@ -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,8 @@
1
+ {
2
+ "version": "2.0",
3
+ "logging": {
4
+ "applicationInsights": {
5
+ "samplingSettings": { "isEnabled": true }
6
+ }
7
+ }
8
+ }
@@ -0,0 +1,7 @@
1
+ # $$Name$$ - basic environment (remote server)
2
+
3
+ Runs the backend like local, on a plain server under systemd.
4
+
5
+ BASIC_HOST=user@host sh gen/env/basic/deploy.sh
6
+
7
+ Installs to /opt/$$name$$/backend with unit $$name$$-backend.service.
@@ -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,7 @@
1
+ # $$Name$$ backend - Cloudflare Workers config (generated; starting point)
2
+ name = "$$name$$-backend"
3
+ main = "../../../src/env/cloudflare/worker.ts"
4
+ compatibility_date = "2026-01-01"
5
+
6
+ [vars]
7
+ NODE_ENV = "production"
@@ -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,7 @@
1
+ # $$Name$$ - docker environment
2
+
3
+ cd backend && npm run build
4
+ docker compose -f gen/env/docker/compose.yml up --build
5
+
6
+ Port $$port$$. The image runs the local (in-memory) runner; wire a real
7
+ store before production use.
@@ -0,0 +1,10 @@
1
+ # $$Name$$ backend - docker compose (generated; customize via tm/env/docker/)
2
+ services:
3
+ $$name$$-backend:
4
+ build:
5
+ context: ../../..
6
+ dockerfile: gen/env/docker/Dockerfile
7
+ ports:
8
+ - "$$port$$:$$port$$"
9
+ environment:
10
+ NODE_ENV: production
@@ -0,0 +1,7 @@
1
+ # $$Name$$ - local environment
2
+
3
+ Runs the backend in-process with the in-memory store:
4
+
5
+ sh gen/env/local/run.sh # or: cd backend && npm run local
6
+
7
+ Port: $$port$$. No external services required.
@@ -0,0 +1,6 @@
1
+ #!/bin/sh
2
+ # $$Name$$ backend - local run (generated; customize via tm/env/local/)
3
+ # In-process Seneca instance, in-memory store, port $$port$$.
4
+ cd "$(dirname "$0")/../../.."
5
+ export NODE_ENV=development
6
+ exec npm run local