@xemahq/create-biome 0.1.3 → 0.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/README.md +27 -10
- package/dist/bin/create-biome.js +95 -44
- package/dist/bin/create-biome.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/lib/host-wiring.d.ts +10 -0
- package/dist/lib/host-wiring.d.ts.map +1 -0
- package/dist/lib/host-wiring.js +70 -0
- package/dist/lib/host-wiring.js.map +1 -0
- package/dist/lib/kind.d.ts +15 -0
- package/dist/lib/kind.d.ts.map +1 -0
- package/dist/lib/kind.js +33 -0
- package/dist/lib/kind.js.map +1 -0
- package/dist/lib/manifest-builder.d.ts +54 -14
- package/dist/lib/manifest-builder.d.ts.map +1 -1
- package/dist/lib/manifest-builder.js +54 -12
- package/dist/lib/manifest-builder.js.map +1 -1
- package/dist/lib/scaffolder.d.ts +12 -6
- package/dist/lib/scaffolder.d.ts.map +1 -1
- package/dist/lib/scaffolder.js +70 -21
- package/dist/lib/scaffolder.js.map +1 -1
- package/dist/lib/template-files.d.ts +7 -2
- package/dist/lib/template-files.d.ts.map +1 -1
- package/dist/lib/template-files.js +473 -222
- package/dist/lib/template-files.js.map +1 -1
- package/package.json +13 -13
- package/LICENSE +0 -201
- package/dist/lib/package-builder.d.ts +0 -17
- package/dist/lib/package-builder.d.ts.map +0 -1
- package/dist/lib/package-builder.js +0 -25
- package/dist/lib/package-builder.js.map +0 -1
- package/dist/lib/template.d.ts +0 -14
- package/dist/lib/template.d.ts.map +0 -1
- package/dist/lib/template.js +0 -26
- package/dist/lib/template.js.map +0 -1
|
@@ -1,263 +1,514 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
case BiomeTemplateId.Connector:
|
|
5
|
-
return renderConnectorTemplate(ctx);
|
|
6
|
-
case BiomeTemplateId.WorkflowsOnly:
|
|
7
|
-
return renderWorkflowsOnlyTemplate(ctx);
|
|
8
|
-
}
|
|
1
|
+
import { apiNameForBiome } from './manifest-builder.js';
|
|
2
|
+
function camelize(kebab) {
|
|
3
|
+
return kebab.replaceAll(/-([a-z0-9])/g, (_, ch) => ch.toUpperCase());
|
|
9
4
|
}
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
"outDir": "dist",
|
|
14
|
-
"rootDir": "src",
|
|
15
|
-
"module": "ESNext",
|
|
16
|
-
"moduleResolution": "Bundler"
|
|
17
|
-
},
|
|
18
|
-
"include": ["src/**/*.ts"]
|
|
5
|
+
function pascalize(kebab) {
|
|
6
|
+
const camel = camelize(kebab);
|
|
7
|
+
return camel.charAt(0).toUpperCase() + camel.slice(1);
|
|
19
8
|
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
9
|
+
function constCase(kebab) {
|
|
10
|
+
return kebab.replaceAll('-', '_').toUpperCase();
|
|
11
|
+
}
|
|
12
|
+
function serverBiomeRootFiles(ctx) {
|
|
13
|
+
return {
|
|
14
|
+
'README.md': `# ${ctx.displayName}
|
|
25
15
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
include: ['src/**/*.test.ts'],
|
|
30
|
-
},
|
|
31
|
-
});
|
|
32
|
-
`;
|
|
33
|
-
function readmeFor(ctx, summary) {
|
|
34
|
-
return `# ${ctx.displayName}
|
|
16
|
+
${ctx.description}
|
|
17
|
+
|
|
18
|
+
A first-party Xema **server biome** scaffolded by \`@xemahq/create-biome\`.
|
|
35
19
|
|
|
36
|
-
|
|
20
|
+
## Layout
|
|
37
21
|
|
|
38
|
-
|
|
22
|
+
- \`xema-biome.json\` — the biome manifest (validated by \`BiomeManifestSchema\` at boot).
|
|
23
|
+
- \`api/${apiNameForBiome(ctx.biomeId)}/\` — the NestJS service this biome ships,
|
|
24
|
+
bootstrapped by \`XemaServiceModule.forBiome(...)\`.
|
|
25
|
+
- \`skills/\` — Skill folder bundles (\`<slug>/SKILL.md\`) seeded into the agent runtime.
|
|
26
|
+
- \`agents/\` — Agent definitions (\`<slug>.md\`); declare each in \`xema.agents[]\`.
|
|
27
|
+
- \`contributions/\` — \`*.contribution.json\` envelopes (capabilities, etc.).
|
|
28
|
+
- \`workspace-manifests/\` — \`<slug>.workspace.yaml\` agent workspace manifests.
|
|
29
|
+
|
|
30
|
+
## After scaffolding
|
|
39
31
|
|
|
40
32
|
\`\`\`sh
|
|
33
|
+
# from the monorepo root
|
|
41
34
|
pnpm install
|
|
42
|
-
|
|
43
|
-
pnpm
|
|
35
|
+
node tooling/codegen/generate-service-bootstrap.mjs # emit the bootstrap descriptor
|
|
36
|
+
pnpm refresh # openapi -> client -> build
|
|
44
37
|
\`\`\`
|
|
38
|
+
`,
|
|
39
|
+
'skills/README.md': `# Skills
|
|
40
|
+
|
|
41
|
+
Drop Skill folder bundles here, one per directory:
|
|
45
42
|
|
|
46
|
-
|
|
43
|
+
\`\`\`
|
|
44
|
+
skills/<slug>/SKILL.md
|
|
45
|
+
\`\`\`
|
|
47
46
|
|
|
48
|
-
|
|
49
|
-
|
|
47
|
+
\`SKILL.md\` is the only strict file (YAML frontmatter \`name\` + \`description\`,
|
|
48
|
+
then a free-form markdown body). \`reference/\`, \`scripts/\`, and \`assets/\` are
|
|
49
|
+
optional and mounted as-is. \`scope\` and \`phases\` are NOT frontmatter — they
|
|
50
|
+
are server-resolved.
|
|
51
|
+
`,
|
|
52
|
+
'agents/README.md': `# Agents
|
|
50
53
|
|
|
51
|
-
|
|
54
|
+
Drop agent definitions here, one \`<slug>.md\` per agent. Each agent MUST also
|
|
55
|
+
be declared in \`xema-biome.json\` under \`xema.agents[]\` (\`slug\` + \`mode\`) AND
|
|
56
|
+
\`xema.ships.content\` must include \`"agents"\` — the boot-time cross-validator
|
|
57
|
+
enforces parity between the manifest roster and the on-disk files.
|
|
58
|
+
`,
|
|
59
|
+
'contributions/.gitkeep': '',
|
|
60
|
+
'workspace-manifests/README.md': `# Workspace manifests
|
|
52
61
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
62
|
+
Drop agent workspace manifests here, one \`<slug>.workspace.yaml\` per agent
|
|
63
|
+
workspace. See \`xema://manifest/primary-agent-base\` for the base to extend.
|
|
64
|
+
`,
|
|
65
|
+
};
|
|
56
66
|
}
|
|
57
|
-
function
|
|
67
|
+
function serverApiFiles(ctx) {
|
|
68
|
+
const apiName = apiNameForBiome(ctx.biomeId);
|
|
69
|
+
const apiDir = `api/${apiName}`;
|
|
70
|
+
const constName = `${constCase(apiName)}_BOOTSTRAP`;
|
|
71
|
+
const pascal = pascalize(ctx.biomeId);
|
|
72
|
+
const featureModule = `${pascal}Module`;
|
|
73
|
+
const featureController = `${pascal}Controller`;
|
|
74
|
+
const featureService = `${pascal}Service`;
|
|
58
75
|
return {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
76
|
+
[`${apiDir}/package.json`]: `${JSON.stringify({
|
|
77
|
+
name: apiName,
|
|
78
|
+
version: '0.1.0',
|
|
79
|
+
private: true,
|
|
80
|
+
scripts: {
|
|
81
|
+
clean: 'rm -rf dist',
|
|
82
|
+
build: 'nest build',
|
|
83
|
+
format: 'prettier --write "src/**/*.ts"',
|
|
84
|
+
start: 'nest start',
|
|
85
|
+
'start:dev': 'nest start --watch',
|
|
86
|
+
lint: 'eslint "src/**/*.ts"',
|
|
87
|
+
'lint:fix': 'eslint "src/**/*.ts" --fix',
|
|
88
|
+
typecheck: 'tsc --noEmit',
|
|
89
|
+
openapi: 'NODE_PATH=node_modules xema-openapi',
|
|
90
|
+
'client:generate': 'xema-client-generate',
|
|
91
|
+
},
|
|
92
|
+
dependencies: {
|
|
93
|
+
'@nestjs/common': 'catalog:',
|
|
94
|
+
'@nestjs/config': 'catalog:',
|
|
95
|
+
'@nestjs/core': 'catalog:',
|
|
96
|
+
'@nestjs/platform-express': 'catalog:',
|
|
97
|
+
'@nestjs/swagger': 'catalog:',
|
|
98
|
+
'@xemahq/identity-client': 'catalog:',
|
|
99
|
+
'@xemahq/kernel-contracts': 'catalog:',
|
|
100
|
+
'@xemahq/platform-common': 'catalog:',
|
|
101
|
+
'@xemahq/service-registry-nest': 'catalog:',
|
|
102
|
+
'@xemahq/xema-decorators': 'catalog:',
|
|
103
|
+
'@xemahq/xema-service-nest': 'catalog:',
|
|
104
|
+
'class-transformer': 'catalog:',
|
|
105
|
+
'class-validator': 'catalog:',
|
|
106
|
+
'reflect-metadata': 'catalog:',
|
|
107
|
+
rxjs: 'catalog:',
|
|
108
|
+
'swagger-ui-express': 'catalog:',
|
|
109
|
+
},
|
|
110
|
+
devDependencies: {
|
|
111
|
+
'@eslint/js': 'catalog:',
|
|
112
|
+
'@nestjs/cli': 'catalog:',
|
|
113
|
+
'@nestjs/schematics': 'catalog:',
|
|
114
|
+
'@types/express': 'catalog:',
|
|
115
|
+
'@types/node': 'catalog:',
|
|
116
|
+
'@xemahq/api-client-generator': 'catalog:',
|
|
117
|
+
dotenv: 'catalog:',
|
|
118
|
+
eslint: 'catalog:',
|
|
119
|
+
'eslint-config-prettier': 'catalog:',
|
|
120
|
+
orval: 'catalog:',
|
|
121
|
+
prettier: 'catalog:',
|
|
122
|
+
typescript: 'catalog:',
|
|
123
|
+
'typescript-eslint': 'catalog:',
|
|
124
|
+
},
|
|
125
|
+
xema: {
|
|
126
|
+
api: {
|
|
127
|
+
serviceId: apiName,
|
|
128
|
+
biomeId: ctx.biomeId,
|
|
129
|
+
title: `${ctx.displayName} API`,
|
|
130
|
+
description: ctx.description,
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
}, null, 2)}\n`,
|
|
134
|
+
[`${apiDir}/tsconfig.json`]: `${JSON.stringify({
|
|
135
|
+
extends: '../../../../tsconfig.base.json',
|
|
136
|
+
compilerOptions: {
|
|
137
|
+
outDir: 'dist',
|
|
138
|
+
rootDir: 'src',
|
|
139
|
+
types: ['node'],
|
|
140
|
+
baseUrl: '.',
|
|
141
|
+
},
|
|
142
|
+
include: ['src/**/*.ts'],
|
|
143
|
+
exclude: ['node_modules', 'dist'],
|
|
144
|
+
}, null, 2)}\n`,
|
|
145
|
+
[`${apiDir}/nest-cli.json`]: `${JSON.stringify({
|
|
146
|
+
$schema: 'https://json.schemastore.org/nest-cli',
|
|
147
|
+
collection: '@nestjs/schematics',
|
|
148
|
+
sourceRoot: 'src',
|
|
149
|
+
compilerOptions: {
|
|
150
|
+
deleteOutDir: true,
|
|
151
|
+
plugins: [
|
|
152
|
+
{
|
|
153
|
+
name: '@nestjs/swagger',
|
|
154
|
+
options: {
|
|
155
|
+
classValidatorShim: true,
|
|
156
|
+
introspectComments: true,
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
],
|
|
160
|
+
},
|
|
161
|
+
}, null, 2)}\n`,
|
|
162
|
+
[`${apiDir}/.gitignore`]: `node_modules
|
|
163
|
+
dist
|
|
164
|
+
.turbo
|
|
165
|
+
openapi.*.json
|
|
166
|
+
`,
|
|
167
|
+
[`${apiDir}/src/generated/${apiName}.bootstrap.generated.ts`]: renderBootstrapPlaceholder(apiName, constName, `${ctx.displayName} API`, ctx.biomeId),
|
|
168
|
+
[`${apiDir}/src/main.ts`]: renderMainTs(apiName, ctx),
|
|
169
|
+
[`${apiDir}/src/app.module.ts`]: renderAppModule(apiName, constName, featureModule),
|
|
170
|
+
[`${apiDir}/src/config/index.ts`]: renderConfigIndex(),
|
|
171
|
+
[`${apiDir}/src/health/health.module.ts`]: HEALTH_MODULE,
|
|
172
|
+
[`${apiDir}/src/health/health.controller.ts`]: HEALTH_CONTROLLER,
|
|
173
|
+
[`${apiDir}/src/${ctx.biomeId}/${ctx.biomeId}.module.ts`]: renderFeatureModule(ctx.biomeId, featureModule, featureController, featureService),
|
|
174
|
+
[`${apiDir}/src/${ctx.biomeId}/${ctx.biomeId}.controller.ts`]: renderFeatureController(ctx.biomeId, featureController, featureService),
|
|
175
|
+
[`${apiDir}/src/${ctx.biomeId}/${ctx.biomeId}.service.ts`]: renderFeatureService(ctx.biomeId, featureService),
|
|
68
176
|
};
|
|
69
177
|
}
|
|
70
|
-
function
|
|
71
|
-
return `//
|
|
72
|
-
//
|
|
73
|
-
|
|
178
|
+
function renderBootstrapPlaceholder(apiName, constName, displayName, biomeId) {
|
|
179
|
+
return `// AUTO-GENERATED by tooling/codegen/generate-service-bootstrap.mjs — DO NOT EDIT.
|
|
180
|
+
// Source of truth: biomes/${biomeId}/xema-biome.json (xema.ships.apis[] + top-level version).
|
|
181
|
+
// Regenerate: \`node tooling/codegen/generate-service-bootstrap.mjs\`.
|
|
182
|
+
import {
|
|
183
|
+
ServiceKind,
|
|
184
|
+
type BiomeServiceDescriptor,
|
|
185
|
+
} from '@xemahq/xema-service-nest';
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Bootstrap identity for ${apiName}, consumed by
|
|
189
|
+
* \`XemaServiceModule.forBiome(${constName})\`. Edit the biome manifest,
|
|
190
|
+
* not this file.
|
|
191
|
+
*/
|
|
192
|
+
export const ${constName}: BiomeServiceDescriptor = {
|
|
193
|
+
name: '${apiName}',
|
|
194
|
+
semver: '0.1.0',
|
|
195
|
+
serviceKind: ServiceKind.BiomeApi,
|
|
196
|
+
displayName: '${displayName}',
|
|
197
|
+
requiresServices: ['identity-api'],
|
|
198
|
+
exposesCapabilities: [],
|
|
199
|
+
};
|
|
74
200
|
`;
|
|
75
201
|
}
|
|
76
|
-
function
|
|
77
|
-
return
|
|
78
|
-
|
|
79
|
-
|
|
202
|
+
function renderMainTs(apiName, ctx) {
|
|
203
|
+
return `import { bootstrapXemaService } from '@xemahq/xema-service-nest';
|
|
204
|
+
|
|
205
|
+
import { AppModule } from './app.module';
|
|
206
|
+
import { requiredEnvVars } from './config';
|
|
207
|
+
|
|
208
|
+
const swaggerDescription = \`
|
|
209
|
+
${ctx.description}
|
|
210
|
+
\`.trim();
|
|
211
|
+
|
|
212
|
+
void bootstrapXemaService({
|
|
213
|
+
appModule: AppModule,
|
|
214
|
+
serviceName: '${apiName}',
|
|
215
|
+
swaggerTitle: '${ctx.displayName} API',
|
|
216
|
+
swaggerDescription,
|
|
217
|
+
requiredEnvVars,
|
|
218
|
+
});
|
|
80
219
|
`;
|
|
81
220
|
}
|
|
82
|
-
function
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
} from '@xemahq/biome-sdk/adapter';
|
|
93
|
-
import {
|
|
94
|
-
BuiltInAdapterKind,
|
|
95
|
-
CredentialFieldTransform,
|
|
96
|
-
CredentialFieldType,
|
|
97
|
-
CredentialKind,
|
|
98
|
-
IntegrationOnboardingKind,
|
|
99
|
-
ProjectBindingAdapterKey,
|
|
100
|
-
type ProviderOnboardingManifest,
|
|
101
|
-
} from '@xemahq/kernel-contracts/connector';
|
|
102
|
-
|
|
103
|
-
// Replace the placeholder verifier below with the real signature
|
|
104
|
-
// algorithm your provider uses. The kernel ships hmac-sha256,
|
|
105
|
-
// hmac-sha1, ed25519, and 'none' (static-token); biomes re-use the
|
|
106
|
-
// same primitive so the registry never has to know provider-specific
|
|
107
|
-
// details.
|
|
108
|
-
const verifier: WebhookVerifier = {
|
|
109
|
-
signatureHeader: 'x-${ctx.biomeId}-signature',
|
|
110
|
-
algorithm: 'hmac-sha256',
|
|
111
|
-
secretSource: 'org-integration-secret',
|
|
112
|
-
verify(_input) {
|
|
113
|
-
return err(
|
|
114
|
-
adapterError('internal', 'replace this with a real verify() before shipping'),
|
|
115
|
-
);
|
|
116
|
-
},
|
|
117
|
-
};
|
|
118
|
-
|
|
119
|
-
const eventMapper: EventMapper = {
|
|
120
|
-
map({ rawEvent, headers }) {
|
|
121
|
-
// Discriminate on the provider's event header or payload field,
|
|
122
|
-
// then map onto the canonical envelope for your AdapterKind.
|
|
123
|
-
if (!rawEvent || typeof rawEvent !== 'object') {
|
|
124
|
-
return err(adapterError('malformed-payload', 'event body is not an object'));
|
|
125
|
-
}
|
|
126
|
-
// \`headers\` is lower-cased; useful when the discriminator lives
|
|
127
|
-
// on a header instead of the payload.
|
|
128
|
-
void headers;
|
|
129
|
-
return ok(null);
|
|
130
|
-
},
|
|
131
|
-
};
|
|
221
|
+
function renderAppModule(apiName, constName, featureModule) {
|
|
222
|
+
const featureFile = featureModule
|
|
223
|
+
.replace(/Module$/, '')
|
|
224
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1-$2')
|
|
225
|
+
.toLowerCase();
|
|
226
|
+
return `import { Module } from '@nestjs/common';
|
|
227
|
+
import { ConfigModule } from '@nestjs/config';
|
|
228
|
+
import { APP_INTERCEPTOR } from '@nestjs/core';
|
|
229
|
+
import { ResponseEnvelopeInterceptor } from '@xemahq/platform-common';
|
|
230
|
+
import { XemaServiceModule } from '@xemahq/xema-service-nest';
|
|
132
231
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
};
|
|
232
|
+
import { appConfig } from './config';
|
|
233
|
+
import { ${constName} } from './generated/${apiName}.bootstrap.generated';
|
|
234
|
+
import { HealthModule } from './health/health.module';
|
|
235
|
+
import { ${featureModule} } from './${featureFile}/${featureFile}.module';
|
|
138
236
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
//
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
// Declare which project-binding adapter kinds your provider supports.
|
|
150
|
-
// Keep this list explicit — an empty array means the provider has no
|
|
151
|
-
// project-level bindings today and the project Bind dialog will hide
|
|
152
|
-
// it. Available keys: SCM, TRACKER, DOCUMENTATION.
|
|
153
|
-
supportedProjectBindingAdapterKinds: [] as readonly ProjectBindingAdapterKey[],
|
|
154
|
-
fields: [
|
|
155
|
-
{
|
|
156
|
-
key: 'apiToken',
|
|
157
|
-
label: 'API Token',
|
|
158
|
-
type: CredentialFieldType.PASSWORD,
|
|
159
|
-
required: true,
|
|
160
|
-
placeholder: 'Paste the provider API token',
|
|
161
|
-
transform: CredentialFieldTransform.TRIM,
|
|
162
|
-
},
|
|
237
|
+
@Module({
|
|
238
|
+
imports: [
|
|
239
|
+
ConfigModule.forRoot({ isGlobal: true, load: [appConfig] }),
|
|
240
|
+
// One-call platform bootstrap: Service Registry + Identity bootstrap +
|
|
241
|
+
// Auth (JWT + request context) + the runtime route/capability scanner.
|
|
242
|
+
// Service identity is the single source of truth in xema-biome.json,
|
|
243
|
+
// surfaced via the generated descriptor.
|
|
244
|
+
XemaServiceModule.forBiome(${constName}),
|
|
245
|
+
HealthModule,
|
|
246
|
+
${featureModule},
|
|
163
247
|
],
|
|
164
|
-
|
|
165
|
-
}
|
|
248
|
+
providers: [
|
|
249
|
+
{ provide: APP_INTERCEPTOR, useClass: ResponseEnvelopeInterceptor },
|
|
250
|
+
],
|
|
251
|
+
})
|
|
252
|
+
export class AppModule {}
|
|
253
|
+
`;
|
|
254
|
+
}
|
|
255
|
+
function renderConfigIndex() {
|
|
256
|
+
return `import { registerAs } from '@nestjs/config';
|
|
166
257
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
eventMapper,
|
|
174
|
-
idempotencyKeyExtractor,
|
|
175
|
-
},
|
|
176
|
-
credentialKind: CredentialKind.OAuthUser,
|
|
177
|
-
resources: {},
|
|
178
|
-
actions: {},
|
|
179
|
-
onboarding,
|
|
180
|
-
});
|
|
258
|
+
/**
|
|
259
|
+
* Environment variables this service requires at boot. \`bootstrapXemaService\`
|
|
260
|
+
* fail-fasts if any is missing. The platform bootstrap injects the registry /
|
|
261
|
+
* identity wiring; add service-specific required vars here as the biome grows.
|
|
262
|
+
*/
|
|
263
|
+
export const requiredEnvVars: readonly string[] = ['IDENTITY_API_URL'];
|
|
181
264
|
|
|
182
|
-
export
|
|
265
|
+
export const appConfig = registerAs('app', () => ({
|
|
266
|
+
port: Number(process.env.PORT ?? '3000'),
|
|
267
|
+
}));
|
|
183
268
|
`;
|
|
184
269
|
}
|
|
185
|
-
|
|
186
|
-
return `import { describe, expect, it } from 'vitest';
|
|
270
|
+
const HEALTH_MODULE = `import { Module } from '@nestjs/common';
|
|
187
271
|
|
|
188
|
-
import {
|
|
272
|
+
import { HealthController } from './health.controller';
|
|
189
273
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
expect(${camelize(ctx.biomeId)}ProviderModule.provider).toBe('${ctx.biomeId}');
|
|
193
|
-
expect(${camelize(ctx.biomeId)}ProviderModule.adapterKind).toBeTruthy();
|
|
194
|
-
});
|
|
195
|
-
});
|
|
274
|
+
@Module({ controllers: [HealthController] })
|
|
275
|
+
export class HealthModule {}
|
|
196
276
|
`;
|
|
277
|
+
const HEALTH_CONTROLLER = `import { Controller, Get } from '@nestjs/common';
|
|
278
|
+
import { XemaPublicRoute } from '@xemahq/xema-decorators';
|
|
279
|
+
|
|
280
|
+
@Controller('health')
|
|
281
|
+
export class HealthController {
|
|
282
|
+
@Get('live')
|
|
283
|
+
@XemaPublicRoute()
|
|
284
|
+
live(): { status: 'ok' } {
|
|
285
|
+
return { status: 'ok' };
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
@Get('ready')
|
|
289
|
+
@XemaPublicRoute()
|
|
290
|
+
ready(): { status: 'ok' } {
|
|
291
|
+
return { status: 'ok' };
|
|
292
|
+
}
|
|
197
293
|
}
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
import {
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
expect(parsed.data.id).toBe('${ctx.biomeId}');
|
|
212
|
-
});
|
|
213
|
-
});
|
|
294
|
+
`;
|
|
295
|
+
function renderFeatureModule(biomeId, featureModule, featureController, featureService) {
|
|
296
|
+
const file = biomeId;
|
|
297
|
+
return `import { Module } from '@nestjs/common';
|
|
298
|
+
|
|
299
|
+
import { ${featureController} } from './${file}.controller';
|
|
300
|
+
import { ${featureService} } from './${file}.service';
|
|
301
|
+
|
|
302
|
+
@Module({
|
|
303
|
+
controllers: [${featureController}],
|
|
304
|
+
providers: [${featureService}],
|
|
305
|
+
})
|
|
306
|
+
export class ${featureModule} {}
|
|
214
307
|
`;
|
|
215
308
|
}
|
|
216
|
-
function
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
'contracts/index.ts': renderContractsIndex(ctx),
|
|
223
|
-
'agents/index.ts': renderAgentsIndex(ctx),
|
|
224
|
-
'workflow-config/workflows/sample.yaml': renderSampleWorkflow(ctx),
|
|
225
|
-
'src/index.ts': `// Biomes without modules export an empty placeholder. Add typed
|
|
226
|
-
// helpers here if your YAML workflows reference custom activities.
|
|
227
|
-
// defineBiome() will be called here once you add contributions.
|
|
228
|
-
export {};
|
|
229
|
-
`,
|
|
230
|
-
'src/index.test.ts': `import { describe, it, expect } from 'vitest';
|
|
309
|
+
function renderFeatureController(biomeId, featureController, featureService) {
|
|
310
|
+
const file = biomeId;
|
|
311
|
+
const serviceVar = camelize(biomeId);
|
|
312
|
+
return `import { Controller, Get } from '@nestjs/common';
|
|
313
|
+
import { ApiOkResponse, ApiTags } from '@nestjs/swagger';
|
|
314
|
+
import { XemaRoute } from '@xemahq/xema-decorators';
|
|
231
315
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
})
|
|
237
|
-
|
|
238
|
-
|
|
316
|
+
import { ${featureService} } from './${file}.service';
|
|
317
|
+
import { ${pascalizeForDto(biomeId)}StatusDto } from './${file}.service';
|
|
318
|
+
|
|
319
|
+
@ApiTags('${biomeId}')
|
|
320
|
+
@Controller('${biomeId}')
|
|
321
|
+
export class ${featureController} {
|
|
322
|
+
constructor(private readonly ${serviceVar}Service: ${featureService}) {}
|
|
323
|
+
|
|
324
|
+
@Get('status')
|
|
325
|
+
@XemaRoute()
|
|
326
|
+
@ApiOkResponse({ type: ${pascalizeForDto(biomeId)}StatusDto })
|
|
327
|
+
status(): ${pascalizeForDto(biomeId)}StatusDto {
|
|
328
|
+
return this.${serviceVar}Service.status();
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
`;
|
|
332
|
+
}
|
|
333
|
+
function pascalizeForDto(biomeId) {
|
|
334
|
+
return pascalize(biomeId);
|
|
335
|
+
}
|
|
336
|
+
function renderFeatureService(biomeId, featureService) {
|
|
337
|
+
const pascal = pascalize(biomeId);
|
|
338
|
+
return `import { Injectable } from '@nestjs/common';
|
|
339
|
+
import { ApiProperty } from '@nestjs/swagger';
|
|
340
|
+
|
|
341
|
+
/** Sample response DTO — replace with the biome's real domain types. */
|
|
342
|
+
export class ${pascal}StatusDto {
|
|
343
|
+
@ApiProperty({ example: '${biomeId}' })
|
|
344
|
+
biome!: string;
|
|
345
|
+
|
|
346
|
+
@ApiProperty({ example: 'ok' })
|
|
347
|
+
status!: string;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
@Injectable()
|
|
351
|
+
export class ${featureService} {
|
|
352
|
+
status(): ${pascal}StatusDto {
|
|
353
|
+
return { biome: '${biomeId}', status: 'ok' };
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
`;
|
|
357
|
+
}
|
|
358
|
+
export function webBiomeFiles(ctx) {
|
|
359
|
+
const pageComponent = `${pascalize(ctx.biomeId)}Page`;
|
|
360
|
+
const navId = ctx.biomeId;
|
|
361
|
+
return {
|
|
362
|
+
'package.json': `${JSON.stringify({
|
|
363
|
+
name: `@xemahq/biomes-${ctx.webBiomeId}`,
|
|
364
|
+
version: '0.1.0',
|
|
365
|
+
description: ctx.description,
|
|
366
|
+
license: 'Apache-2.0',
|
|
367
|
+
private: true,
|
|
368
|
+
type: 'module',
|
|
369
|
+
main: 'src/index.tsx',
|
|
370
|
+
types: 'src/index.tsx',
|
|
371
|
+
files: ['src', 'xema-biome.json'],
|
|
372
|
+
scripts: {
|
|
373
|
+
typecheck: 'tsc --noEmit',
|
|
374
|
+
lint: 'tsc --noEmit',
|
|
375
|
+
format: 'prettier --write "src/**/*.{ts,tsx}"',
|
|
376
|
+
},
|
|
377
|
+
peerDependencies: {
|
|
378
|
+
'@tanstack/react-query': '^5.0.0',
|
|
379
|
+
react: '^18.3.1',
|
|
380
|
+
'react-dom': '^18.3.1',
|
|
381
|
+
},
|
|
382
|
+
devDependencies: {
|
|
383
|
+
'@tanstack/react-query': '^5.83.0',
|
|
384
|
+
'@types/react': '^18.3.0',
|
|
385
|
+
'@types/react-dom': '^18.3.0',
|
|
386
|
+
'lucide-react': '^0.462.0',
|
|
387
|
+
prettier: '3.6.2',
|
|
388
|
+
react: '^18.3.1',
|
|
389
|
+
'react-dom': '^18.3.1',
|
|
390
|
+
typescript: '5.9.3',
|
|
391
|
+
},
|
|
392
|
+
}, null, 2)}\n`,
|
|
393
|
+
'tsconfig.json': WEB_TSCONFIG,
|
|
394
|
+
'src/index.tsx': renderWebIndex(ctx, pageComponent, navId),
|
|
395
|
+
[`src/pages/${pageComponent}.tsx`]: renderWebPage(ctx, pageComponent),
|
|
239
396
|
};
|
|
240
397
|
}
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
398
|
+
const WEB_TSCONFIG = `{
|
|
399
|
+
"compilerOptions": {
|
|
400
|
+
"target": "ES2022",
|
|
401
|
+
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
|
402
|
+
"module": "ESNext",
|
|
403
|
+
"moduleResolution": "bundler",
|
|
404
|
+
"jsx": "react-jsx",
|
|
405
|
+
"noEmit": true,
|
|
406
|
+
"strict": true,
|
|
407
|
+
"esModuleInterop": true,
|
|
408
|
+
"skipLibCheck": true,
|
|
409
|
+
"allowSyntheticDefaultImports": true,
|
|
410
|
+
"isolatedModules": true,
|
|
411
|
+
"useDefineForClassFields": true,
|
|
412
|
+
// Mirror the HOST root tsconfig — biome sources import host primitives
|
|
413
|
+
// authored under the host's compiler flags.
|
|
414
|
+
"exactOptionalPropertyTypes": false,
|
|
415
|
+
"baseUrl": ".",
|
|
416
|
+
"paths": {
|
|
417
|
+
"@/*": ["../../../src/*"],
|
|
418
|
+
"@xemahq/ui-kernel": ["../../../src/lib/shared/ui-kernel/index.ts"],
|
|
419
|
+
"@xemahq/ui-kernel/*": ["../../../src/lib/shared/ui-kernel/*"],
|
|
420
|
+
"@xemahq/*": ["../../../src/lib/shared/*", "../../../src/lib/api/orval/clients/*"]
|
|
421
|
+
}
|
|
422
|
+
},
|
|
423
|
+
"include": ["src/**/*.ts", "src/**/*.tsx"]
|
|
424
|
+
}
|
|
425
|
+
`;
|
|
426
|
+
function renderWebIndex(ctx, pageComponent, navId) {
|
|
427
|
+
return `import {
|
|
428
|
+
type FrontendBiome,
|
|
429
|
+
type FrontendBiomeFactory,
|
|
430
|
+
type HostBridge,
|
|
431
|
+
} from '@xemahq/ui-kernel';
|
|
432
|
+
import { LayoutGrid } from 'lucide-react';
|
|
433
|
+
import { Suspense, createElement, lazy } from 'react';
|
|
434
|
+
|
|
435
|
+
// Lazy-load the page so its dependency graph is fetched only when the user
|
|
436
|
+
// navigates into the route, not at biome registration.
|
|
437
|
+
const ${pageComponent} = lazy(() => import('./pages/${pageComponent}'));
|
|
438
|
+
|
|
439
|
+
/**
|
|
440
|
+
* Frontend module entry-point for the \`${ctx.webBiomeId}\` biome.
|
|
441
|
+
*
|
|
442
|
+
* The host bootstrap fetches \`GET /platform/biomes/web\`, dynamically imports
|
|
443
|
+
* this module for any entry where \`enabled === true\`, then calls this default
|
|
444
|
+
* export with its concrete \`HostBridge\` and registers the result via
|
|
445
|
+
* \`registerFrontendBiome()\` from \`@xemahq/ui-kernel\`.
|
|
446
|
+
*/
|
|
447
|
+
const ${camelize(ctx.webBiomeId)}Biome: FrontendBiomeFactory = (
|
|
448
|
+
_bridge: HostBridge,
|
|
449
|
+
): FrontendBiome => {
|
|
450
|
+
return {
|
|
451
|
+
id: '${ctx.webBiomeId}',
|
|
452
|
+
displayName: '${ctx.navLabel}',
|
|
453
|
+
navItems: [
|
|
454
|
+
{
|
|
455
|
+
id: '${navId}',
|
|
456
|
+
label: '${ctx.navLabel}',
|
|
457
|
+
route: '${navId}',
|
|
458
|
+
icon: LayoutGrid,
|
|
459
|
+
section: 'Workspace',
|
|
460
|
+
weight: 50,
|
|
461
|
+
},
|
|
462
|
+
],
|
|
463
|
+
routes: [
|
|
464
|
+
{
|
|
465
|
+
path: '${navId}',
|
|
466
|
+
projectScoped: true,
|
|
467
|
+
element: () =>
|
|
468
|
+
createElement(
|
|
469
|
+
Suspense,
|
|
470
|
+
{ fallback: null },
|
|
471
|
+
createElement(${pageComponent}),
|
|
472
|
+
),
|
|
473
|
+
},
|
|
474
|
+
],
|
|
475
|
+
};
|
|
476
|
+
};
|
|
477
|
+
|
|
478
|
+
export default ${camelize(ctx.webBiomeId)}Biome;
|
|
258
479
|
`;
|
|
259
480
|
}
|
|
260
|
-
function
|
|
261
|
-
return
|
|
481
|
+
function renderWebPage(ctx, pageComponent) {
|
|
482
|
+
return `import { useBiomeRouteParams } from '@/lib/biomes/biome-host-next';
|
|
483
|
+
|
|
484
|
+
/**
|
|
485
|
+
* Sample project-scoped page for the \`${ctx.webBiomeId}\` biome. Reads the
|
|
486
|
+
* project id bound by the biome route matcher via the host
|
|
487
|
+
* \`useBiomeRouteParams\` bridge hook (NEVER \`next/navigation\` — biome pages
|
|
488
|
+
* must go through the host's route-params context).
|
|
489
|
+
*/
|
|
490
|
+
export default function ${pageComponent}(): JSX.Element {
|
|
491
|
+
const { projectId } = useBiomeRouteParams<{ projectId?: string }>();
|
|
492
|
+
|
|
493
|
+
return (
|
|
494
|
+
<div className="p-6">
|
|
495
|
+
<h1 className="text-xl font-semibold">${ctx.navLabel}</h1>
|
|
496
|
+
<p className="mt-2 text-sm text-muted-foreground">
|
|
497
|
+
${ctx.description}
|
|
498
|
+
</p>
|
|
499
|
+
<p className="mt-4 text-sm">
|
|
500
|
+
Project scope:{' '}
|
|
501
|
+
<code>{projectId ?? '(none — open from a project)'}</code>
|
|
502
|
+
</p>
|
|
503
|
+
</div>
|
|
504
|
+
);
|
|
505
|
+
}
|
|
506
|
+
`;
|
|
507
|
+
}
|
|
508
|
+
export function renderServerBiomeFiles(ctx) {
|
|
509
|
+
return {
|
|
510
|
+
...serverBiomeRootFiles(ctx),
|
|
511
|
+
...serverApiFiles(ctx),
|
|
512
|
+
};
|
|
262
513
|
}
|
|
263
514
|
//# sourceMappingURL=template-files.js.map
|