@lssm/module.contractspec-workspace 0.0.0-canary-20251217083314 → 1.41.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ai/code-generation.js +13 -50
- package/dist/ai/spec-creation.js +18 -50
- package/dist/analysis/deps/graph.js +2 -84
- package/dist/analysis/deps/parse-imports.js +1 -30
- package/dist/analysis/diff/semantic.js +1 -96
- package/dist/analysis/feature-scan.js +1 -151
- package/dist/analysis/spec-scan.js +1 -344
- package/dist/analysis/validate/spec-structure.js +1 -122
- package/dist/index.js +1 -25
- package/dist/templates/app-config.js +28 -100
- package/dist/templates/data-view.js +27 -41
- package/dist/templates/event.js +14 -28
- package/dist/templates/experiment.js +51 -76
- package/dist/templates/handler.js +17 -49
- package/dist/templates/integration-utils.js +26 -97
- package/dist/templates/integration.js +23 -46
- package/dist/templates/knowledge.js +19 -59
- package/dist/templates/migration.js +26 -49
- package/dist/templates/operation.js +28 -40
- package/dist/templates/presentation.js +20 -45
- package/dist/templates/telemetry.js +53 -73
- package/dist/templates/utils.js +1 -38
- package/dist/templates/workflow-runner.js +6 -12
- package/dist/templates/workflow.js +24 -50
- package/dist/types/generation-types.js +1 -20
- package/package.json +6 -6
- package/dist/ai/code-generation.d.ts +0 -27
- package/dist/ai/spec-creation.d.ts +0 -26
- package/dist/analysis/deps/graph.d.ts +0 -33
- package/dist/analysis/deps/parse-imports.d.ts +0 -16
- package/dist/analysis/diff/semantic.d.ts +0 -10
- package/dist/analysis/feature-scan.d.ts +0 -14
- package/dist/analysis/spec-scan.d.ts +0 -33
- package/dist/analysis/validate/spec-structure.d.ts +0 -10
- package/dist/index.d.ts +0 -26
- package/dist/templates/app-config.d.ts +0 -6
- package/dist/templates/data-view.d.ts +0 -6
- package/dist/templates/event.d.ts +0 -10
- package/dist/templates/experiment.d.ts +0 -6
- package/dist/templates/handler.d.ts +0 -19
- package/dist/templates/integration.d.ts +0 -6
- package/dist/templates/knowledge.d.ts +0 -6
- package/dist/templates/migration.d.ts +0 -6
- package/dist/templates/operation.d.ts +0 -10
- package/dist/templates/presentation.d.ts +0 -10
- package/dist/templates/telemetry.d.ts +0 -6
- package/dist/templates/utils.d.ts +0 -26
- package/dist/templates/workflow-runner.d.ts +0 -15
- package/dist/templates/workflow.d.ts +0 -10
- package/dist/types/analysis-types.d.ts +0 -125
- package/dist/types/generation-types.d.ts +0 -83
- package/dist/types/spec-types.d.ts +0 -344
|
@@ -1,105 +1,33 @@
|
|
|
1
|
-
import
|
|
1
|
+
import{toPascalCase as e}from"./utils.js";function t(t){let d=e(t.name.split(`.`).pop()??`App`)+`AppConfig`,f=n(t),m=r(t),h=i(`dataViews`,t.dataViews),g=i(`workflows`,t.workflows),_=a(t),v=o(t),y=s(t),b=c(t),x=l(t),S=u(t),C=t.notes?` notes: '${p(t.notes)}',\n`:``;return`import type { AppBlueprintSpec } from '@lssm/lib.contracts/app-config';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
function generateAppBlueprintSpec(data) {
|
|
5
|
-
const exportName = toPascalCase(data.name.split(".").pop() ?? "App") + "AppConfig";
|
|
6
|
-
const capabilitiesSection = buildCapabilitiesSection(data);
|
|
7
|
-
const featuresSection = buildFeaturesSection(data);
|
|
8
|
-
const dataViewsSection = buildMappingSection("dataViews", data.dataViews);
|
|
9
|
-
const workflowsSection = buildMappingSection("workflows", data.workflows);
|
|
10
|
-
const policiesSection = buildPolicySection(data);
|
|
11
|
-
const themeSection = buildThemeSection(data);
|
|
12
|
-
const telemetrySection = buildTelemetrySection(data);
|
|
13
|
-
const experimentsSection = buildExperimentsSection(data);
|
|
14
|
-
const flagsSection = buildFeatureFlagsSection(data);
|
|
15
|
-
const routesSection = buildRoutesSection(data);
|
|
16
|
-
const notesSection = data.notes ? ` notes: '${escapeString(data.notes)}',\n` : "";
|
|
17
|
-
return `import type { AppBlueprintSpec } from '@lssm/lib.contracts/app-config';
|
|
18
|
-
|
|
19
|
-
export const ${exportName}: AppBlueprintSpec = {
|
|
3
|
+
export const ${d}: AppBlueprintSpec = {
|
|
20
4
|
meta: {
|
|
21
|
-
name: '${
|
|
22
|
-
version: ${
|
|
23
|
-
title: '${
|
|
24
|
-
description: '${
|
|
25
|
-
domain: '${
|
|
26
|
-
owners: [${
|
|
27
|
-
tags: [${
|
|
28
|
-
stability: '${
|
|
29
|
-
appId: '${
|
|
5
|
+
name: '${p(t.name)}',
|
|
6
|
+
version: ${t.version},
|
|
7
|
+
title: '${p(t.title)}',
|
|
8
|
+
description: '${p(t.description)}',
|
|
9
|
+
domain: '${p(t.domain)}',
|
|
10
|
+
owners: [${t.owners.map(e=>`'${p(e)}'`).join(`, `)}],
|
|
11
|
+
tags: [${t.tags.map(e=>`'${p(e)}'`).join(`, `)}],
|
|
12
|
+
stability: '${t.stability}',
|
|
13
|
+
appId: '${p(t.appId)}',
|
|
30
14
|
},
|
|
31
|
-
${
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
return ` features: {\n${data.featureIncludes.length > 0 ? ` include: [${data.featureIncludes.map((key) => `{ key: '${escapeString(key)}' }`).join(", ")}],\n` : ""}${data.featureExcludes.length > 0 ? ` exclude: [${data.featureExcludes.map((key) => `{ key: '${escapeString(key)}' }`).join(", ")}],\n` : ""} },\n`;
|
|
40
|
-
}
|
|
41
|
-
function buildMappingSection(prop, mappings) {
|
|
42
|
-
if (mappings.length === 0) return "";
|
|
43
|
-
return ` ${prop}: {\n${mappings.map((mapping) => ` ${mapping.slot}: {
|
|
44
|
-
name: '${escapeString(mapping.name)}',
|
|
45
|
-
${typeof mapping.version === "number" ? `version: ${mapping.version},` : ""}
|
|
46
|
-
}`).join(",\n")}\n },\n`;
|
|
47
|
-
}
|
|
48
|
-
function buildPolicySection(data) {
|
|
49
|
-
if (data.policyRefs.length === 0) return "";
|
|
50
|
-
return ` policies: [\n${data.policyRefs.map((policy) => ` {
|
|
51
|
-
name: '${escapeString(policy.name)}'${typeof policy.version === "number" ? `,\n version: ${policy.version}` : ""}
|
|
52
|
-
}`).join(",\n")}\n ],\n`;
|
|
53
|
-
}
|
|
54
|
-
function buildThemeSection(data) {
|
|
55
|
-
if (!data.theme) return "";
|
|
56
|
-
return ` theme: {\n${` primary: { name: '${escapeString(data.theme.name)}', version: ${data.theme.version} },\n`}${data.themeFallbacks.length > 0 ? ` fallbacks: [${data.themeFallbacks.map((theme) => `{ name: '${escapeString(theme.name)}', version: ${theme.version} }`).join(", ")}],\n` : ""} },\n`;
|
|
57
|
-
}
|
|
58
|
-
function buildTelemetrySection(data) {
|
|
59
|
-
if (!data.telemetry) return "";
|
|
60
|
-
return ` telemetry: {
|
|
15
|
+
${f}${m}${h}${g}${_}${v}${y}${b}${x}${S}${C}};\n`}function n(e){return e.capabilitiesEnabled.length===0&&e.capabilitiesDisabled.length===0?``:` capabilities: {\n${e.capabilitiesEnabled.length>0?` enabled: [${e.capabilitiesEnabled.map(e=>d(e)).join(`, `)}],\n`:``}${e.capabilitiesDisabled.length>0?` disabled: [${e.capabilitiesDisabled.map(e=>d(e)).join(`, `)}],\n`:``} },\n`}function r(e){return e.featureIncludes.length===0&&e.featureExcludes.length===0?``:` features: {\n${e.featureIncludes.length>0?` include: [${e.featureIncludes.map(e=>`{ key: '${p(e)}' }`).join(`, `)}],\n`:``}${e.featureExcludes.length>0?` exclude: [${e.featureExcludes.map(e=>`{ key: '${p(e)}' }`).join(`, `)}],\n`:``} },\n`}function i(e,t){return t.length===0?``:` ${e}: {\n${t.map(e=>` ${e.slot}: {
|
|
16
|
+
name: '${p(e.name)}',
|
|
17
|
+
${typeof e.version==`number`?`version: ${e.version},`:``}
|
|
18
|
+
}`).join(`,
|
|
19
|
+
`)}\n },\n`}function a(e){return e.policyRefs.length===0?``:` policies: [\n${e.policyRefs.map(e=>` {
|
|
20
|
+
name: '${p(e.name)}'${typeof e.version==`number`?`,\n version: ${e.version}`:``}
|
|
21
|
+
}`).join(`,
|
|
22
|
+
`)}\n ],\n`}function o(e){return e.theme?` theme: {\n${` primary: { name: '${p(e.theme.name)}', version: ${e.theme.version} },\n`}${e.themeFallbacks.length>0?` fallbacks: [${e.themeFallbacks.map(e=>`{ name: '${p(e.name)}', version: ${e.version} }`).join(`, `)}],\n`:``} },\n`:``}function s(e){return e.telemetry?` telemetry: {
|
|
61
23
|
spec: {
|
|
62
|
-
name: '${
|
|
24
|
+
name: '${p(e.telemetry.name)}'${typeof e.telemetry.version==`number`?`,\n version: ${e.telemetry.version}`:``}
|
|
63
25
|
},
|
|
64
|
-
},\n
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
}
|
|
70
|
-
function
|
|
71
|
-
|
|
72
|
-
return ` featureFlags: [\n${data.featureFlags.map((flag) => ` {
|
|
73
|
-
key: '${escapeString(flag.key)}',
|
|
74
|
-
enabled: ${flag.enabled},
|
|
75
|
-
${flag.variant ? `variant: '${escapeString(flag.variant)}',` : ""}
|
|
76
|
-
${flag.description ? `description: '${escapeString(flag.description)}',` : ""}
|
|
77
|
-
}`).join(",\n")}\n ],\n`;
|
|
78
|
-
}
|
|
79
|
-
function buildRoutesSection(data) {
|
|
80
|
-
if (data.routes.length === 0) return "";
|
|
81
|
-
return ` routes: [\n${data.routes.map((route) => {
|
|
82
|
-
return ` { ${[
|
|
83
|
-
`path: '${escapeString(route.path)}'`,
|
|
84
|
-
route.label ? `label: '${escapeString(route.label)}'` : null,
|
|
85
|
-
route.dataView ? `dataView: '${escapeString(route.dataView)}'` : null,
|
|
86
|
-
route.workflow ? `workflow: '${escapeString(route.workflow)}'` : null,
|
|
87
|
-
route.guardName ? `guard: { name: '${escapeString(route.guardName)}'${typeof route.guardVersion === "number" ? `, version: ${route.guardVersion}` : ""} }` : null,
|
|
88
|
-
route.featureFlag ? `featureFlag: '${escapeString(route.featureFlag)}'` : null,
|
|
89
|
-
route.experimentName ? `experiment: { name: '${escapeString(route.experimentName)}'${typeof route.experimentVersion === "number" ? `, version: ${route.experimentVersion}` : ""} }` : null
|
|
90
|
-
].filter(Boolean).join(", ")} }`;
|
|
91
|
-
}).join(",\n")}\n ],\n`;
|
|
92
|
-
}
|
|
93
|
-
function formatCapabilityRef(key) {
|
|
94
|
-
return `{ key: '${escapeString(key)}' }`;
|
|
95
|
-
}
|
|
96
|
-
function formatExperimentRef(exp) {
|
|
97
|
-
const version = typeof exp.version === "number" ? `, version: ${exp.version}` : "";
|
|
98
|
-
return `{ name: '${escapeString(exp.name)}'${version} }`;
|
|
99
|
-
}
|
|
100
|
-
function escapeString(value) {
|
|
101
|
-
return value.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
//#endregion
|
|
105
|
-
export { generateAppBlueprintSpec };
|
|
26
|
+
},\n`:``}function c(e){return e.activeExperiments.length===0&&e.pausedExperiments.length===0?``:` experiments: {\n${e.activeExperiments.length>0?` active: [${e.activeExperiments.map(e=>f(e)).join(`, `)}],\n`:``}${e.pausedExperiments.length>0?` paused: [${e.pausedExperiments.map(e=>f(e)).join(`, `)}],\n`:``} },\n`}function l(e){return e.featureFlags.length===0?``:` featureFlags: [\n${e.featureFlags.map(e=>` {
|
|
27
|
+
key: '${p(e.key)}',
|
|
28
|
+
enabled: ${e.enabled},
|
|
29
|
+
${e.variant?`variant: '${p(e.variant)}',`:``}
|
|
30
|
+
${e.description?`description: '${p(e.description)}',`:``}
|
|
31
|
+
}`).join(`,
|
|
32
|
+
`)}\n ],\n`}function u(e){return e.routes.length===0?``:` routes: [\n${e.routes.map(e=>` { ${[`path: '${p(e.path)}'`,e.label?`label: '${p(e.label)}'`:null,e.dataView?`dataView: '${p(e.dataView)}'`:null,e.workflow?`workflow: '${p(e.workflow)}'`:null,e.guardName?`guard: { name: '${p(e.guardName)}'${typeof e.guardVersion==`number`?`, version: ${e.guardVersion}`:``} }`:null,e.featureFlag?`featureFlag: '${p(e.featureFlag)}'`:null,e.experimentName?`experiment: { name: '${p(e.experimentName)}'${typeof e.experimentVersion==`number`?`, version: ${e.experimentVersion}`:``} }`:null].filter(Boolean).join(`, `)} }`).join(`,
|
|
33
|
+
`)}\n ],\n`}function d(e){return`{ key: '${p(e)}' }`}function f(e){let t=typeof e.version==`number`?`, version: ${e.version}`:``;return`{ name: '${p(e.name)}'${t} }`}function p(e){return e.replace(/\\/g,`\\\\`).replace(/'/g,`\\'`)}export{t as generateAppBlueprintSpec};
|
|
@@ -1,49 +1,42 @@
|
|
|
1
|
-
import
|
|
1
|
+
import{escapeString as e,toPascalCase as t}from"./utils.js";function n(n){let i=t(n.name.split(`.`).pop()??`DataView`)+`DataView`,a=n.fields.map(t=>` {
|
|
2
|
+
key: '${e(t.key)}',
|
|
3
|
+
label: '${r(t.label)}',
|
|
4
|
+
dataPath: '${e(t.dataPath)}',
|
|
5
|
+
${t.format?`format: '${e(t.format)}',`:``}
|
|
6
|
+
${t.sortable?`sortable: true,`:``}
|
|
7
|
+
${t.filterable?`filterable: true,`:``}
|
|
8
|
+
}`).join(`,
|
|
9
|
+
`),o=n.secondaryFields?.length?`secondaryFields: [${n.secondaryFields.map(t=>`'${e(t)}'`).join(`, `)}],`:``,s=n.itemOperation?`item: { name: '${e(n.itemOperation.name)}', version: ${n.itemOperation.version} },`:``;return`import type { DataViewSpec } from '@lssm/lib.contracts/data-views';
|
|
2
10
|
|
|
3
|
-
|
|
4
|
-
function generateDataViewSpec(data) {
|
|
5
|
-
const viewVarName = toPascalCase(data.name.split(".").pop() ?? "DataView") + "DataView";
|
|
6
|
-
const fields = data.fields.map((field) => ` {
|
|
7
|
-
key: '${escapeString(field.key)}',
|
|
8
|
-
label: '${escape(field.label)}',
|
|
9
|
-
dataPath: '${escapeString(field.dataPath)}',
|
|
10
|
-
${field.format ? `format: '${escapeString(field.format)}',` : ""}
|
|
11
|
-
${field.sortable ? "sortable: true," : ""}
|
|
12
|
-
${field.filterable ? "filterable: true," : ""}
|
|
13
|
-
}`).join(",\n");
|
|
14
|
-
const secondaryFields = data.secondaryFields?.length ? `secondaryFields: [${data.secondaryFields.map((key) => `'${escapeString(key)}'`).join(", ")}],` : "";
|
|
15
|
-
const itemOperation = data.itemOperation ? `item: { name: '${escapeString(data.itemOperation.name)}', version: ${data.itemOperation.version} },` : "";
|
|
16
|
-
return `import type { DataViewSpec } from '@lssm/lib.contracts/data-views';
|
|
17
|
-
|
|
18
|
-
export const ${viewVarName}: DataViewSpec = {
|
|
11
|
+
export const ${i}: DataViewSpec = {
|
|
19
12
|
meta: {
|
|
20
|
-
name: '${
|
|
21
|
-
version: ${
|
|
22
|
-
entity: '${
|
|
23
|
-
title: '${
|
|
24
|
-
description: '${
|
|
25
|
-
domain: '${
|
|
26
|
-
owners: [${
|
|
27
|
-
tags: [${
|
|
28
|
-
stability: '${
|
|
13
|
+
name: '${e(n.name)}',
|
|
14
|
+
version: ${n.version},
|
|
15
|
+
entity: '${e(n.entity)}',
|
|
16
|
+
title: '${r(n.title)}',
|
|
17
|
+
description: '${r(n.description||`Describe the purpose of this data view.`)}',
|
|
18
|
+
domain: '${r(n.domain||n.entity)}',
|
|
19
|
+
owners: [${n.owners.map(t=>`'${e(t)}'`).join(`, `)}],
|
|
20
|
+
tags: [${n.tags.map(t=>`'${e(t)}'`).join(`, `)}],
|
|
21
|
+
stability: '${n.stability}',
|
|
29
22
|
},
|
|
30
23
|
source: {
|
|
31
24
|
primary: {
|
|
32
|
-
name: '${
|
|
33
|
-
version: ${
|
|
25
|
+
name: '${e(n.primaryOperation.name)}',
|
|
26
|
+
version: ${n.primaryOperation.version},
|
|
34
27
|
},
|
|
35
|
-
${
|
|
28
|
+
${s}
|
|
36
29
|
refreshEvents: [
|
|
37
30
|
// { name: 'entity.updated', version: 1 },
|
|
38
31
|
],
|
|
39
32
|
},
|
|
40
33
|
view: {
|
|
41
|
-
kind: '${
|
|
34
|
+
kind: '${n.kind}',
|
|
42
35
|
fields: [
|
|
43
|
-
${
|
|
36
|
+
${a}
|
|
44
37
|
],
|
|
45
|
-
${
|
|
46
|
-
${
|
|
38
|
+
${n.primaryField?`primaryField: '${e(n.primaryField)}',`:``}
|
|
39
|
+
${o}
|
|
47
40
|
filters: [
|
|
48
41
|
// Example filter:
|
|
49
42
|
// { key: 'search', label: 'Search', field: 'fullName', type: 'search' },
|
|
@@ -58,11 +51,4 @@ ${fields}
|
|
|
58
51
|
// error: { name: 'app.data.error', version: 1 },
|
|
59
52
|
},
|
|
60
53
|
};
|
|
61
|
-
|
|
62
|
-
}
|
|
63
|
-
function escape(value) {
|
|
64
|
-
return value.replace(/'/g, "\\'");
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
//#endregion
|
|
68
|
-
export { generateDataViewSpec };
|
|
54
|
+
`}function r(e){return e.replace(/'/g,`\\'`)}export{n as generateDataViewSpec};
|
package/dist/templates/event.js
CHANGED
|
@@ -1,38 +1,24 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
//#region src/templates/event.ts
|
|
4
|
-
/**
|
|
5
|
-
* Generate event spec TypeScript code.
|
|
6
|
-
*/
|
|
7
|
-
function generateEventSpec(data) {
|
|
8
|
-
const { name, version, description, stability, owners, tags, piiFields } = data;
|
|
9
|
-
const eventVarName = toPascalCase(name.replace(/\./g, "_")) + "V" + version;
|
|
10
|
-
const payloadSchemaName = eventVarName + "Payload";
|
|
11
|
-
return `import { defineEvent } from '@lssm/lib.contracts';
|
|
1
|
+
import{toPascalCase as e}from"./utils.js";function t(t){let{name:n,version:r,description:i,stability:a,owners:o,tags:s,piiFields:c}=t,l=e(n.replace(/\./g,`_`))+`V`+r,u=l+`Payload`;return`import { defineEvent } from '@lssm/lib.contracts';
|
|
12
2
|
import { ScalarTypeEnum, SchemaModel } from '@lssm/lib.schema';
|
|
13
3
|
|
|
14
4
|
// TODO: Define event payload schema
|
|
15
|
-
export const ${
|
|
16
|
-
name: '${
|
|
17
|
-
description: 'Payload for ${
|
|
5
|
+
export const ${u} = new SchemaModel({
|
|
6
|
+
name: '${u}',
|
|
7
|
+
description: 'Payload for ${n}',
|
|
18
8
|
fields: {
|
|
19
9
|
// Add your payload fields here
|
|
20
10
|
// example: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
21
11
|
},
|
|
22
12
|
});
|
|
23
13
|
|
|
24
|
-
export const ${
|
|
25
|
-
name: '${
|
|
26
|
-
version: ${
|
|
27
|
-
description: '${
|
|
28
|
-
stability: '${
|
|
29
|
-
owners: [${
|
|
30
|
-
tags: [${
|
|
31
|
-
${
|
|
32
|
-
payload: ${
|
|
14
|
+
export const ${l} = defineEvent({
|
|
15
|
+
name: '${n}',
|
|
16
|
+
version: ${r},
|
|
17
|
+
description: '${i}',
|
|
18
|
+
stability: '${a}',
|
|
19
|
+
owners: [${o.map(e=>`'${e}'`).join(`, `)}],
|
|
20
|
+
tags: [${s.map(e=>`'${e}'`).join(`, `)}],
|
|
21
|
+
${c.length>0?`pii: [${c.map(e=>`'${e}'`).join(`, `)}],`:`// pii: [],`}
|
|
22
|
+
payload: ${u},
|
|
33
23
|
});
|
|
34
|
-
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
//#endregion
|
|
38
|
-
export { generateEventSpec };
|
|
24
|
+
`}export{t as generateEventSpec};
|
|
@@ -1,87 +1,62 @@
|
|
|
1
|
-
import
|
|
1
|
+
import{toPascalCase as e}from"./utils.js";function t(t){let i=e(t.name.split(`.`).pop()??`Experiment`)+`Experiment`,a=t.variants.map(e=>{let t=e.overrides?.length?` overrides: [
|
|
2
|
+
${e.overrides.map(e=>` {
|
|
3
|
+
type: '${e.type}',
|
|
4
|
+
target: '${r(e.target)}',
|
|
5
|
+
${typeof e.version==`number`?`version: ${e.version},`:``}
|
|
6
|
+
}`).join(`,
|
|
7
|
+
`)}
|
|
8
|
+
],`:``;return` {
|
|
9
|
+
id: '${r(e.id)}',
|
|
10
|
+
name: '${r(e.name)}',
|
|
11
|
+
${e.description?`description: '${r(e.description)}',`:``}
|
|
12
|
+
${typeof e.weight==`number`?`weight: ${e.weight},`:``}
|
|
13
|
+
${t}
|
|
14
|
+
}`}).join(`,
|
|
15
|
+
`),o=n(t.allocation),s=t.successMetrics?.length?` successMetrics: [
|
|
16
|
+
${t.successMetrics.map(e=>` {
|
|
17
|
+
name: '${r(e.name)}',
|
|
18
|
+
telemetryEvent: { name: '${r(e.eventName)}', version: ${e.eventVersion} },
|
|
19
|
+
aggregation: '${e.aggregation}',
|
|
20
|
+
${typeof e.target==`number`?`target: ${e.target},`:``}
|
|
21
|
+
}`).join(`,
|
|
22
|
+
`)}
|
|
23
|
+
],`:``;return`import type { ExperimentSpec } from '@lssm/lib.contracts/experiments';
|
|
2
24
|
|
|
3
|
-
|
|
4
|
-
function generateExperimentSpec(data) {
|
|
5
|
-
const specVar = toPascalCase(data.name.split(".").pop() ?? "Experiment") + "Experiment";
|
|
6
|
-
const variants = data.variants.map((variant) => {
|
|
7
|
-
const overrides = variant.overrides?.length ? ` overrides: [
|
|
8
|
-
${variant.overrides.map((override) => ` {
|
|
9
|
-
type: '${override.type}',
|
|
10
|
-
target: '${escapeString(override.target)}',
|
|
11
|
-
${typeof override.version === "number" ? `version: ${override.version},` : ""}
|
|
12
|
-
}`).join(",\n")}
|
|
13
|
-
],` : "";
|
|
14
|
-
return ` {
|
|
15
|
-
id: '${escapeString(variant.id)}',
|
|
16
|
-
name: '${escapeString(variant.name)}',
|
|
17
|
-
${variant.description ? `description: '${escapeString(variant.description)}',` : ""}
|
|
18
|
-
${typeof variant.weight === "number" ? `weight: ${variant.weight},` : ""}
|
|
19
|
-
${overrides}
|
|
20
|
-
}`;
|
|
21
|
-
}).join(",\n");
|
|
22
|
-
const allocation = renderAllocation(data.allocation);
|
|
23
|
-
const metrics = data.successMetrics?.length ? ` successMetrics: [
|
|
24
|
-
${data.successMetrics.map((metric) => ` {
|
|
25
|
-
name: '${escapeString(metric.name)}',
|
|
26
|
-
telemetryEvent: { name: '${escapeString(metric.eventName)}', version: ${metric.eventVersion} },
|
|
27
|
-
aggregation: '${metric.aggregation}',
|
|
28
|
-
${typeof metric.target === "number" ? `target: ${metric.target},` : ""}
|
|
29
|
-
}`).join(",\n")}
|
|
30
|
-
],` : "";
|
|
31
|
-
return `import type { ExperimentSpec } from '@lssm/lib.contracts/experiments';
|
|
32
|
-
|
|
33
|
-
export const ${specVar}: ExperimentSpec = {
|
|
25
|
+
export const ${i}: ExperimentSpec = {
|
|
34
26
|
meta: {
|
|
35
|
-
name: '${
|
|
36
|
-
version: ${
|
|
37
|
-
title: '${
|
|
38
|
-
description: '${
|
|
39
|
-
domain: '${
|
|
40
|
-
owners: [${
|
|
41
|
-
tags: [${
|
|
42
|
-
stability: '${
|
|
27
|
+
name: '${r(t.name)}',
|
|
28
|
+
version: ${t.version},
|
|
29
|
+
title: '${r(t.name)} experiment',
|
|
30
|
+
description: '${r(t.description||`Describe the experiment goal.`)}',
|
|
31
|
+
domain: '${r(t.domain)}',
|
|
32
|
+
owners: [${t.owners.map(e=>`'${r(e)}'`).join(`, `)}],
|
|
33
|
+
tags: [${t.tags.map(e=>`'${r(e)}'`).join(`, `)}],
|
|
34
|
+
stability: '${t.stability}',
|
|
43
35
|
},
|
|
44
|
-
controlVariant: '${
|
|
36
|
+
controlVariant: '${r(t.controlVariant)}',
|
|
45
37
|
variants: [
|
|
46
|
-
${
|
|
38
|
+
${a}
|
|
47
39
|
],
|
|
48
|
-
allocation: ${
|
|
49
|
-
${
|
|
40
|
+
allocation: ${o},
|
|
41
|
+
${s}
|
|
50
42
|
};
|
|
51
|
-
|
|
52
|
-
}
|
|
53
|
-
function renderAllocation(allocation) {
|
|
54
|
-
switch (allocation.type) {
|
|
55
|
-
case "random": return `{
|
|
43
|
+
`}function n(e){switch(e.type){case`random`:return`{
|
|
56
44
|
type: 'random',
|
|
57
|
-
${
|
|
58
|
-
}`;
|
|
59
|
-
case "sticky": return `{
|
|
45
|
+
${e.salt?`salt: '${r(e.salt)}',`:``}
|
|
46
|
+
}`;case`sticky`:return`{
|
|
60
47
|
type: 'sticky',
|
|
61
|
-
attribute: '${
|
|
62
|
-
${
|
|
63
|
-
}`;
|
|
64
|
-
case "targeted": return `{
|
|
48
|
+
attribute: '${e.attribute}',
|
|
49
|
+
${e.salt?`salt: '${r(e.salt)}',`:``}
|
|
50
|
+
}`;case`targeted`:return`{
|
|
65
51
|
type: 'targeted',
|
|
66
52
|
rules: [
|
|
67
|
-
${
|
|
68
|
-
variantId: '${
|
|
69
|
-
${typeof
|
|
70
|
-
${
|
|
71
|
-
${
|
|
72
|
-
}`).join(
|
|
53
|
+
${e.rules.map(e=>` {
|
|
54
|
+
variantId: '${r(e.variantId)}',
|
|
55
|
+
${typeof e.percentage==`number`?`percentage: ${e.percentage},`:``}
|
|
56
|
+
${e.policy?`policy: { name: '${r(e.policy.name)}'${typeof e.policy.version==`number`?`, version: ${e.policy.version}`:``} },`:``}
|
|
57
|
+
${e.expression?`expression: '${r(e.expression)}',`:``}
|
|
58
|
+
}`).join(`,
|
|
59
|
+
`)}
|
|
73
60
|
],
|
|
74
|
-
fallback: '${
|
|
75
|
-
}`;
|
|
76
|
-
default: return renderUnsupportedAllocation(allocation);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
function escapeString(value) {
|
|
80
|
-
return value.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
|
|
81
|
-
}
|
|
82
|
-
function renderUnsupportedAllocation(allocation) {
|
|
83
|
-
throw new Error(`Unsupported allocation type ${allocation}`);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
//#endregion
|
|
87
|
-
export { generateExperimentSpec };
|
|
61
|
+
fallback: '${e.fallback??`control`}',
|
|
62
|
+
}`;default:return i(e)}}function r(e){return e.replace(/\\/g,`\\\\`).replace(/'/g,`\\'`)}function i(e){throw Error(`Unsupported allocation type ${e}`)}export{t as generateExperimentSpec};
|
|
@@ -1,27 +1,14 @@
|
|
|
1
|
-
import
|
|
1
|
+
import{toCamelCase as e,toKebabCase as t,toPascalCase as n}from"./utils.js";function r(r,i){let a=e(r.split(`.`).pop()??`unknown`)+`Handler`,o=n(r.split(`.`).pop()??`Unknown`)+`Spec`;return`import type { ContractHandler } from '@lssm/lib.contracts';
|
|
2
|
+
import { ${o} } from '../contracts/${t(r)}.contracts';
|
|
2
3
|
|
|
3
|
-
//#region src/templates/handler.ts
|
|
4
4
|
/**
|
|
5
|
-
* Handler
|
|
6
|
-
* Extracted from cli-contracts/src/templates/handler.template.ts
|
|
7
|
-
*/
|
|
8
|
-
/**
|
|
9
|
-
* Generate handler implementation template.
|
|
10
|
-
*/
|
|
11
|
-
function generateHandlerTemplate(specName, kind) {
|
|
12
|
-
const handlerName = toCamelCase(specName.split(".").pop() ?? "unknown") + "Handler";
|
|
13
|
-
const specVarName = toPascalCase(specName.split(".").pop() ?? "Unknown") + "Spec";
|
|
14
|
-
return `import type { ContractHandler } from '@lssm/lib.contracts';
|
|
15
|
-
import { ${specVarName} } from '../contracts/${toKebabCase(specName)}.contracts';
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Handler for ${specName}
|
|
5
|
+
* Handler for ${r}
|
|
19
6
|
*/
|
|
20
|
-
export const ${
|
|
7
|
+
export const ${a}: ContractHandler<typeof ${o}> = async (
|
|
21
8
|
input,
|
|
22
9
|
context
|
|
23
10
|
) => {
|
|
24
|
-
// TODO: Implement ${
|
|
11
|
+
// TODO: Implement ${i} logic
|
|
25
12
|
|
|
26
13
|
try {
|
|
27
14
|
// 1. Validate prerequisites
|
|
@@ -37,43 +24,28 @@ export const ${handlerName}: ContractHandler<typeof ${specVarName}> = async (
|
|
|
37
24
|
throw error;
|
|
38
25
|
}
|
|
39
26
|
};
|
|
40
|
-
|
|
41
|
-
}
|
|
42
|
-
/**
|
|
43
|
-
* Generate React component template.
|
|
44
|
-
*/
|
|
45
|
-
function generateComponentTemplate(componentName, description) {
|
|
46
|
-
const pascalName = toPascalCase(componentName);
|
|
47
|
-
return `import React from 'react';
|
|
27
|
+
`}function i(e,t){let r=n(e);return`import React from 'react';
|
|
48
28
|
|
|
49
|
-
interface ${
|
|
29
|
+
interface ${r}Props {
|
|
50
30
|
// TODO: Define props based on presentation spec
|
|
51
31
|
}
|
|
52
32
|
|
|
53
33
|
/**
|
|
54
|
-
* ${
|
|
34
|
+
* ${t}
|
|
55
35
|
*/
|
|
56
|
-
export const ${
|
|
36
|
+
export const ${r}: React.FC<${r}Props> = (props) => {
|
|
57
37
|
return (
|
|
58
38
|
<div>
|
|
59
39
|
{/* TODO: Implement component UI */}
|
|
60
|
-
<p>Component: ${
|
|
40
|
+
<p>Component: ${r}</p>
|
|
61
41
|
</div>
|
|
62
42
|
);
|
|
63
43
|
};
|
|
64
|
-
|
|
65
|
-
}
|
|
66
|
-
/**
|
|
67
|
-
* Generate test template.
|
|
68
|
-
*/
|
|
69
|
-
function generateTestTemplate(targetName, type) {
|
|
70
|
-
const importPath = type === "handler" ? "../handlers" : "../components";
|
|
71
|
-
const testName = toPascalCase(targetName);
|
|
72
|
-
return `import { describe, it, expect } from 'bun:test';
|
|
73
|
-
import { ${testName} } from '${importPath}/${toKebabCase(targetName)}';
|
|
44
|
+
`}function a(e,r){let i=r===`handler`?`../handlers`:`../components`,a=n(e);return`import { describe, it, expect } from 'bun:test';
|
|
45
|
+
import { ${a} } from '${i}/${t(e)}';
|
|
74
46
|
|
|
75
|
-
describe('${
|
|
76
|
-
it('should ${
|
|
47
|
+
describe('${a}', () => {
|
|
48
|
+
it('should ${r===`handler`?`handle valid input`:`render correctly`}', async () => {
|
|
77
49
|
// TODO: Implement test
|
|
78
50
|
expect(true).toBe(true);
|
|
79
51
|
});
|
|
@@ -82,14 +54,10 @@ describe('${testName}', () => {
|
|
|
82
54
|
// TODO: Test edge cases
|
|
83
55
|
});
|
|
84
56
|
|
|
85
|
-
${
|
|
57
|
+
${r===`handler`?`it('should handle errors appropriately', async () => {
|
|
86
58
|
// TODO: Test error scenarios
|
|
87
|
-
})
|
|
59
|
+
});`:`it('should be accessible', async () => {
|
|
88
60
|
// TODO: Test accessibility
|
|
89
61
|
});`}
|
|
90
62
|
});
|
|
91
|
-
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
//#endregion
|
|
95
|
-
export { generateComponentTemplate, generateHandlerTemplate, generateTestTemplate };
|
|
63
|
+
`}export{i as generateComponentTemplate,r as generateHandlerTemplate,a as generateTestTemplate};
|