@tradejs/app 3.1.3 → 3.1.5
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/package.json +6 -6
- package/src/app/api/user/runtime-deployments/[deploymentId]/strategies/[strategyName]/control/route.ts +2 -2
- package/src/app/api/user/runtime-deployments/route.ts +52 -57
- package/src/app/api/user/settings/route.ts +14 -30
- package/src/app/api/user/trading-accounts/route.ts +1 -42
- package/src/app/components/Shared/Sidebar/AccountSettingsDrawer.tsx +23 -63
- package/src/app/components/Strategies/RuntimeStrategyCard.tsx +24 -38
- package/src/app/components/Strategies/RuntimeStrategyConfigDrawer.tsx +43 -483
- package/src/app/api/user/runtime-strategy-configs/route.ts +0 -51
- package/src/app/api/user/runtime-strategy-releases/route.ts +0 -60
- package/src/app/lib/runtimeStrategyConfigForm.ts +0 -86
- package/src/app/lib/runtimeStrategyConfigService.ts +0 -279
- package/src/app/lib/runtimeStrategyReleaseService.ts +0 -123
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
import type { StrategyConfig } from '@tradejs/types';
|
|
2
|
-
|
|
3
|
-
export type RuntimeStrategyManagedParameters = {
|
|
4
|
-
maxLossValue: number;
|
|
5
|
-
aiEnabled: boolean;
|
|
6
|
-
aiMode: 'gate' | 'llm';
|
|
7
|
-
minAiQuality: number;
|
|
8
|
-
mlEnabled: boolean;
|
|
9
|
-
mlThreshold: number;
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
export const DEFAULT_RUNTIME_STRATEGY_MANAGED_PARAMETERS: RuntimeStrategyManagedParameters =
|
|
13
|
-
{
|
|
14
|
-
maxLossValue: 1,
|
|
15
|
-
aiEnabled: true,
|
|
16
|
-
aiMode: 'gate',
|
|
17
|
-
minAiQuality: 4,
|
|
18
|
-
mlEnabled: false,
|
|
19
|
-
mlThreshold: 0.1,
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
const FORM_FIELDS = new Set([
|
|
23
|
-
'ENABLE',
|
|
24
|
-
'INTERVAL',
|
|
25
|
-
'UNIVERSE',
|
|
26
|
-
'ACCOUNT_ID',
|
|
27
|
-
'MAX_LOSS_VALUE',
|
|
28
|
-
'AI_ENABLED',
|
|
29
|
-
'AI_MODE',
|
|
30
|
-
'MIN_AI_QUALITY',
|
|
31
|
-
'ML_ENABLED',
|
|
32
|
-
'ML_THRESHOLD',
|
|
33
|
-
]);
|
|
34
|
-
|
|
35
|
-
const readNumber = (value: unknown, fallback: number) => {
|
|
36
|
-
const parsed = Number(value);
|
|
37
|
-
return Number.isFinite(parsed) ? parsed : fallback;
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
const readBoolean = (value: unknown, fallback: boolean) =>
|
|
41
|
-
typeof value === 'boolean' ? value : fallback;
|
|
42
|
-
|
|
43
|
-
export const splitRuntimeStrategyConfig = (config: StrategyConfig | null) => ({
|
|
44
|
-
managed: {
|
|
45
|
-
maxLossValue: readNumber(
|
|
46
|
-
config?.MAX_LOSS_VALUE,
|
|
47
|
-
DEFAULT_RUNTIME_STRATEGY_MANAGED_PARAMETERS.maxLossValue,
|
|
48
|
-
),
|
|
49
|
-
aiEnabled: readBoolean(
|
|
50
|
-
config?.AI_ENABLED,
|
|
51
|
-
DEFAULT_RUNTIME_STRATEGY_MANAGED_PARAMETERS.aiEnabled,
|
|
52
|
-
),
|
|
53
|
-
aiMode:
|
|
54
|
-
config?.AI_MODE === 'llm' || config?.AI_MODE === 'gate'
|
|
55
|
-
? config.AI_MODE
|
|
56
|
-
: DEFAULT_RUNTIME_STRATEGY_MANAGED_PARAMETERS.aiMode,
|
|
57
|
-
minAiQuality: readNumber(
|
|
58
|
-
config?.MIN_AI_QUALITY,
|
|
59
|
-
DEFAULT_RUNTIME_STRATEGY_MANAGED_PARAMETERS.minAiQuality,
|
|
60
|
-
),
|
|
61
|
-
mlEnabled: readBoolean(
|
|
62
|
-
config?.ML_ENABLED,
|
|
63
|
-
DEFAULT_RUNTIME_STRATEGY_MANAGED_PARAMETERS.mlEnabled,
|
|
64
|
-
),
|
|
65
|
-
mlThreshold: readNumber(
|
|
66
|
-
config?.ML_THRESHOLD,
|
|
67
|
-
DEFAULT_RUNTIME_STRATEGY_MANAGED_PARAMETERS.mlThreshold,
|
|
68
|
-
),
|
|
69
|
-
} satisfies RuntimeStrategyManagedParameters,
|
|
70
|
-
parameters: Object.fromEntries(
|
|
71
|
-
Object.entries(config ?? {}).filter(([key]) => !FORM_FIELDS.has(key)),
|
|
72
|
-
),
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
export const mergeRuntimeStrategyManagedParameters = (
|
|
76
|
-
parameters: Record<string, unknown>,
|
|
77
|
-
managed: RuntimeStrategyManagedParameters,
|
|
78
|
-
) => ({
|
|
79
|
-
...parameters,
|
|
80
|
-
MAX_LOSS_VALUE: managed.maxLossValue,
|
|
81
|
-
AI_ENABLED: managed.aiEnabled,
|
|
82
|
-
AI_MODE: managed.aiMode,
|
|
83
|
-
MIN_AI_QUALITY: managed.minAiQuality,
|
|
84
|
-
ML_ENABLED: managed.mlEnabled,
|
|
85
|
-
ML_THRESHOLD: managed.mlThreshold,
|
|
86
|
-
});
|
|
@@ -1,279 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
loadRuntimeStrategyConfigs,
|
|
3
|
-
saveRuntimeStrategyConfig,
|
|
4
|
-
type RuntimeStrategyConfigRecord,
|
|
5
|
-
} from '@tradejs/infra/runtimeStrategyConfigs';
|
|
6
|
-
import {
|
|
7
|
-
listTradingAccounts,
|
|
8
|
-
resolveTradingAccount,
|
|
9
|
-
} from '@tradejs/infra/tradingAccounts';
|
|
10
|
-
import { getAvailableStrategyNames } from '@tradejs/node/strategies';
|
|
11
|
-
import type { Interval, MarketUniverse, StrategyConfig } from '@tradejs/types';
|
|
12
|
-
|
|
13
|
-
export const RUNTIME_STRATEGY_INTERVALS = [
|
|
14
|
-
'1',
|
|
15
|
-
'3',
|
|
16
|
-
'5',
|
|
17
|
-
'15',
|
|
18
|
-
'30',
|
|
19
|
-
'60',
|
|
20
|
-
'120',
|
|
21
|
-
'240',
|
|
22
|
-
'360',
|
|
23
|
-
'720',
|
|
24
|
-
'1440',
|
|
25
|
-
] as const;
|
|
26
|
-
|
|
27
|
-
const intervalSet = new Set<string>(RUNTIME_STRATEGY_INTERVALS);
|
|
28
|
-
|
|
29
|
-
export class RuntimeStrategyConfigServiceError extends Error {
|
|
30
|
-
constructor(
|
|
31
|
-
message: string,
|
|
32
|
-
readonly code: 'conflict' | 'not_found' | 'validation' = 'validation',
|
|
33
|
-
) {
|
|
34
|
-
super(message);
|
|
35
|
-
this.name = 'RuntimeStrategyConfigServiceError';
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
type StoredRuntimeConfig = RuntimeStrategyConfigRecord & {
|
|
40
|
-
config: StrategyConfig;
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
export interface SaveRuntimeStrategyConfigInput {
|
|
44
|
-
strategyName?: unknown;
|
|
45
|
-
configId?: unknown;
|
|
46
|
-
interval?: unknown;
|
|
47
|
-
universe?: unknown;
|
|
48
|
-
accountId?: unknown;
|
|
49
|
-
enabled?: unknown;
|
|
50
|
-
parameters?: unknown;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
const loadConfigs = async (userName: string): Promise<StoredRuntimeConfig[]> =>
|
|
54
|
-
(await loadRuntimeStrategyConfigs(userName)).map(
|
|
55
|
-
({ strategyConfig, ...record }) => ({
|
|
56
|
-
...record,
|
|
57
|
-
strategyConfig,
|
|
58
|
-
config: strategyConfig,
|
|
59
|
-
}),
|
|
60
|
-
);
|
|
61
|
-
|
|
62
|
-
const normalizeConfigId = (value: unknown) => {
|
|
63
|
-
const configId = String(value ?? '').trim();
|
|
64
|
-
if (!configId)
|
|
65
|
-
throw new RuntimeStrategyConfigServiceError('Config id is required');
|
|
66
|
-
if (!/^[a-zA-Z0-9_-]+$/.test(configId)) {
|
|
67
|
-
throw new RuntimeStrategyConfigServiceError(
|
|
68
|
-
'Config id may contain only letters, numbers, _ and -',
|
|
69
|
-
);
|
|
70
|
-
}
|
|
71
|
-
if (configId === 'results') {
|
|
72
|
-
throw new RuntimeStrategyConfigServiceError(
|
|
73
|
-
'Config id "results" is reserved',
|
|
74
|
-
);
|
|
75
|
-
}
|
|
76
|
-
return configId;
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
const normalizeUniverse = (value: unknown): MarketUniverse =>
|
|
80
|
-
value === 'tradfi' ? 'tradfi' : 'crypto';
|
|
81
|
-
|
|
82
|
-
const normalizeInterval = (value: unknown): Interval => {
|
|
83
|
-
const interval = String(value ?? '15');
|
|
84
|
-
if (!intervalSet.has(interval)) {
|
|
85
|
-
throw new RuntimeStrategyConfigServiceError(
|
|
86
|
-
`Unsupported timeframe: ${interval}`,
|
|
87
|
-
);
|
|
88
|
-
}
|
|
89
|
-
return interval as Interval;
|
|
90
|
-
};
|
|
91
|
-
|
|
92
|
-
const normalizeAccountId = (value: unknown) => {
|
|
93
|
-
const accountId = String(value ?? '').trim();
|
|
94
|
-
return accountId || undefined;
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
const resolveEffectiveAccountId = async ({
|
|
98
|
-
userName,
|
|
99
|
-
config,
|
|
100
|
-
}: {
|
|
101
|
-
userName: string;
|
|
102
|
-
config: StrategyConfig;
|
|
103
|
-
}) => {
|
|
104
|
-
const universe = normalizeUniverse(config.UNIVERSE);
|
|
105
|
-
const account = await resolveTradingAccount({
|
|
106
|
-
userName,
|
|
107
|
-
accountId: normalizeAccountId(config.ACCOUNT_ID),
|
|
108
|
-
provider: 'bybit',
|
|
109
|
-
universe,
|
|
110
|
-
});
|
|
111
|
-
return account?.id ?? null;
|
|
112
|
-
};
|
|
113
|
-
|
|
114
|
-
const assertNoEnabledAccountConflict = async ({
|
|
115
|
-
userName,
|
|
116
|
-
strategyName,
|
|
117
|
-
configId,
|
|
118
|
-
config,
|
|
119
|
-
existingConfigs,
|
|
120
|
-
}: {
|
|
121
|
-
userName: string;
|
|
122
|
-
strategyName: string;
|
|
123
|
-
configId: string;
|
|
124
|
-
config: StrategyConfig;
|
|
125
|
-
existingConfigs: StoredRuntimeConfig[];
|
|
126
|
-
}) => {
|
|
127
|
-
if (config.ENABLE === false) return;
|
|
128
|
-
|
|
129
|
-
const accountId = await resolveEffectiveAccountId({ userName, config });
|
|
130
|
-
if (!accountId) {
|
|
131
|
-
throw new RuntimeStrategyConfigServiceError(
|
|
132
|
-
`No enabled Bybit account supports ${normalizeUniverse(config.UNIVERSE)}. Connect an account or save this config as disabled.`,
|
|
133
|
-
);
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
for (const candidate of existingConfigs) {
|
|
137
|
-
if (
|
|
138
|
-
candidate.strategyName !== strategyName ||
|
|
139
|
-
candidate.configId === configId ||
|
|
140
|
-
candidate.config.ENABLE === false
|
|
141
|
-
) {
|
|
142
|
-
continue;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
const candidateAccountId = await resolveEffectiveAccountId({
|
|
146
|
-
userName,
|
|
147
|
-
config: candidate.config,
|
|
148
|
-
});
|
|
149
|
-
if (candidateAccountId === accountId) {
|
|
150
|
-
throw new RuntimeStrategyConfigServiceError(
|
|
151
|
-
`${strategyName} config "${candidate.configId}" already uses account "${accountId}". One strategy can run only once per account.`,
|
|
152
|
-
'conflict',
|
|
153
|
-
);
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
};
|
|
157
|
-
|
|
158
|
-
const toResponseConfig = async (
|
|
159
|
-
userName: string,
|
|
160
|
-
row: StoredRuntimeConfig,
|
|
161
|
-
) => ({
|
|
162
|
-
strategyName: row.strategyName,
|
|
163
|
-
configId: row.configId,
|
|
164
|
-
interval: normalizeInterval(row.config.INTERVAL),
|
|
165
|
-
universe: normalizeUniverse(row.config.UNIVERSE),
|
|
166
|
-
accountId: normalizeAccountId(row.config.ACCOUNT_ID) ?? null,
|
|
167
|
-
effectiveAccountId: await resolveEffectiveAccountId({
|
|
168
|
-
userName,
|
|
169
|
-
config: row.config,
|
|
170
|
-
}).catch(() => null),
|
|
171
|
-
enabled: row.config.ENABLE !== false,
|
|
172
|
-
config: row.config,
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
export const getRuntimeStrategyConfigOptions = async ({
|
|
176
|
-
userName,
|
|
177
|
-
projectRoot,
|
|
178
|
-
}: {
|
|
179
|
-
userName: string;
|
|
180
|
-
projectRoot: string;
|
|
181
|
-
}) => {
|
|
182
|
-
const [configs, strategyNames, accounts] = await Promise.all([
|
|
183
|
-
loadConfigs(userName),
|
|
184
|
-
getAvailableStrategyNames(projectRoot),
|
|
185
|
-
listTradingAccounts(userName),
|
|
186
|
-
]);
|
|
187
|
-
|
|
188
|
-
return {
|
|
189
|
-
configs: await Promise.all(
|
|
190
|
-
configs.map((row) => toResponseConfig(userName, row)),
|
|
191
|
-
),
|
|
192
|
-
strategyNames,
|
|
193
|
-
accounts: accounts.map(
|
|
194
|
-
({ apiKey: _apiKey, apiSecret: _apiSecret, ...account }) => account,
|
|
195
|
-
),
|
|
196
|
-
intervals: [...RUNTIME_STRATEGY_INTERVALS],
|
|
197
|
-
};
|
|
198
|
-
};
|
|
199
|
-
|
|
200
|
-
export const saveRuntimeStrategyConfigForUser = async ({
|
|
201
|
-
userName,
|
|
202
|
-
projectRoot,
|
|
203
|
-
input,
|
|
204
|
-
editing,
|
|
205
|
-
}: {
|
|
206
|
-
userName: string;
|
|
207
|
-
projectRoot: string;
|
|
208
|
-
input: SaveRuntimeStrategyConfigInput;
|
|
209
|
-
editing: boolean;
|
|
210
|
-
}) => {
|
|
211
|
-
const strategyName = String(input.strategyName ?? '').trim();
|
|
212
|
-
const configId = normalizeConfigId(input.configId);
|
|
213
|
-
const availableStrategies = await getAvailableStrategyNames(projectRoot);
|
|
214
|
-
if (!strategyName || !availableStrategies.includes(strategyName)) {
|
|
215
|
-
throw new RuntimeStrategyConfigServiceError(
|
|
216
|
-
`Unknown strategy: ${strategyName || '(empty)'}`,
|
|
217
|
-
);
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
const existingConfigs = await loadConfigs(userName);
|
|
221
|
-
const existing = existingConfigs.find(
|
|
222
|
-
(row) => row.strategyName === strategyName && row.configId === configId,
|
|
223
|
-
);
|
|
224
|
-
if (editing && !existing) {
|
|
225
|
-
throw new RuntimeStrategyConfigServiceError(
|
|
226
|
-
'Runtime strategy config not found',
|
|
227
|
-
'not_found',
|
|
228
|
-
);
|
|
229
|
-
}
|
|
230
|
-
if (!editing && existing) {
|
|
231
|
-
throw new RuntimeStrategyConfigServiceError(
|
|
232
|
-
'Runtime strategy config already exists',
|
|
233
|
-
'conflict',
|
|
234
|
-
);
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
if (
|
|
238
|
-
!input.parameters ||
|
|
239
|
-
typeof input.parameters !== 'object' ||
|
|
240
|
-
Array.isArray(input.parameters)
|
|
241
|
-
) {
|
|
242
|
-
throw new RuntimeStrategyConfigServiceError(
|
|
243
|
-
'Strategy parameters must be a JSON object',
|
|
244
|
-
);
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
const interval = normalizeInterval(input.interval);
|
|
248
|
-
const universe = normalizeUniverse(input.universe);
|
|
249
|
-
const accountId = normalizeAccountId(input.accountId);
|
|
250
|
-
const config: StrategyConfig = {
|
|
251
|
-
...(input.parameters as StrategyConfig),
|
|
252
|
-
ENABLE: input.enabled !== false,
|
|
253
|
-
INTERVAL: interval,
|
|
254
|
-
UNIVERSE: universe,
|
|
255
|
-
};
|
|
256
|
-
if (accountId) config.ACCOUNT_ID = accountId;
|
|
257
|
-
else delete config.ACCOUNT_ID;
|
|
258
|
-
|
|
259
|
-
await assertNoEnabledAccountConflict({
|
|
260
|
-
userName,
|
|
261
|
-
strategyName,
|
|
262
|
-
configId,
|
|
263
|
-
config,
|
|
264
|
-
existingConfigs,
|
|
265
|
-
});
|
|
266
|
-
const saved = await saveRuntimeStrategyConfig({
|
|
267
|
-
userName,
|
|
268
|
-
strategyName,
|
|
269
|
-
configId,
|
|
270
|
-
strategyConfig: config,
|
|
271
|
-
});
|
|
272
|
-
|
|
273
|
-
return {
|
|
274
|
-
config: await toResponseConfig(userName, {
|
|
275
|
-
...saved,
|
|
276
|
-
config,
|
|
277
|
-
}),
|
|
278
|
-
};
|
|
279
|
-
};
|
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
getRuntimeStrategyDraft,
|
|
3
|
-
listRuntimeStrategyReleases,
|
|
4
|
-
publishRuntimeStrategyRelease,
|
|
5
|
-
saveRuntimeStrategyDraft,
|
|
6
|
-
} from '@tradejs/infra/runtimeStrategyReleases';
|
|
7
|
-
import { getRuntimeStrategyPackageMetadata } from '@tradejs/node/runtimeStrategies';
|
|
8
|
-
import {
|
|
9
|
-
getAvailableStrategyNames,
|
|
10
|
-
getStrategyDefaults,
|
|
11
|
-
} from '@tradejs/node/strategies';
|
|
12
|
-
import type { StrategyConfig } from '@tradejs/types';
|
|
13
|
-
|
|
14
|
-
const OPERATIONAL_KEYS = [
|
|
15
|
-
'ACCOUNT_ID',
|
|
16
|
-
'AI_REPLAY_ANALYSES',
|
|
17
|
-
'DEPLOYMENT_ID',
|
|
18
|
-
'ENABLE',
|
|
19
|
-
'ENV',
|
|
20
|
-
'MAKE_ORDERS',
|
|
21
|
-
'RECORD_RUNTIME_TRADES',
|
|
22
|
-
'configId',
|
|
23
|
-
];
|
|
24
|
-
|
|
25
|
-
const assertConfig = (value: unknown): StrategyConfig => {
|
|
26
|
-
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
27
|
-
throw new Error('Strategy config must be a JSON object');
|
|
28
|
-
}
|
|
29
|
-
const config = value as StrategyConfig;
|
|
30
|
-
const invalidKey = OPERATIONAL_KEYS.find((key) => config[key] !== undefined);
|
|
31
|
-
if (invalidKey) {
|
|
32
|
-
throw new Error(
|
|
33
|
-
`${invalidKey} is controlled by the deployment, not config`,
|
|
34
|
-
);
|
|
35
|
-
}
|
|
36
|
-
return config;
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
const assertKnownStrategy = async (
|
|
40
|
-
strategyName: string,
|
|
41
|
-
projectRoot: string,
|
|
42
|
-
) => {
|
|
43
|
-
if (!(await getAvailableStrategyNames(projectRoot)).includes(strategyName)) {
|
|
44
|
-
throw new Error(`Unknown strategy: ${strategyName}`);
|
|
45
|
-
}
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
export const getRuntimeStrategyReleaseOptions = async ({
|
|
49
|
-
userName,
|
|
50
|
-
projectRoot,
|
|
51
|
-
}: {
|
|
52
|
-
userName: string;
|
|
53
|
-
projectRoot: string;
|
|
54
|
-
}) => {
|
|
55
|
-
const strategyNames = await getAvailableStrategyNames(projectRoot);
|
|
56
|
-
const strategies = await Promise.all(
|
|
57
|
-
strategyNames.map(async (strategyName) => ({
|
|
58
|
-
strategyName,
|
|
59
|
-
draft: await getRuntimeStrategyDraft(userName, strategyName),
|
|
60
|
-
releases: await listRuntimeStrategyReleases(userName, strategyName),
|
|
61
|
-
})),
|
|
62
|
-
);
|
|
63
|
-
return { strategyNames, strategies };
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
export const saveRuntimeStrategyReleaseDraftForUser = async ({
|
|
67
|
-
userName,
|
|
68
|
-
projectRoot,
|
|
69
|
-
input,
|
|
70
|
-
}: {
|
|
71
|
-
userName: string;
|
|
72
|
-
projectRoot: string;
|
|
73
|
-
input: Record<string, unknown>;
|
|
74
|
-
}) => {
|
|
75
|
-
const strategyName = String(input.strategyName ?? '').trim();
|
|
76
|
-
await assertKnownStrategy(strategyName, projectRoot);
|
|
77
|
-
return saveRuntimeStrategyDraft({
|
|
78
|
-
userName,
|
|
79
|
-
strategyName,
|
|
80
|
-
config: assertConfig(input.config),
|
|
81
|
-
baseReleaseVersion:
|
|
82
|
-
typeof input.baseReleaseVersion === 'number'
|
|
83
|
-
? input.baseReleaseVersion
|
|
84
|
-
: null,
|
|
85
|
-
updatedBy: userName,
|
|
86
|
-
});
|
|
87
|
-
};
|
|
88
|
-
|
|
89
|
-
export const publishRuntimeStrategyReleaseForUser = async ({
|
|
90
|
-
userName,
|
|
91
|
-
projectRoot,
|
|
92
|
-
strategyName,
|
|
93
|
-
}: {
|
|
94
|
-
userName: string;
|
|
95
|
-
projectRoot: string;
|
|
96
|
-
strategyName: string;
|
|
97
|
-
}) => {
|
|
98
|
-
await assertKnownStrategy(strategyName, projectRoot);
|
|
99
|
-
const draft = await getRuntimeStrategyDraft(userName, strategyName);
|
|
100
|
-
if (!draft) throw new Error(`Draft not found: ${strategyName}`);
|
|
101
|
-
const defaults = (await getStrategyDefaults(strategyName, projectRoot)) ?? {};
|
|
102
|
-
const config: StrategyConfig = { ...defaults, ...draft.config };
|
|
103
|
-
for (const key of OPERATIONAL_KEYS) delete config[key];
|
|
104
|
-
const metadata = await getRuntimeStrategyPackageMetadata({
|
|
105
|
-
strategyName,
|
|
106
|
-
projectRoot,
|
|
107
|
-
});
|
|
108
|
-
const release = await publishRuntimeStrategyRelease({
|
|
109
|
-
userName,
|
|
110
|
-
strategyName,
|
|
111
|
-
config,
|
|
112
|
-
...metadata,
|
|
113
|
-
createdBy: userName,
|
|
114
|
-
});
|
|
115
|
-
await saveRuntimeStrategyDraft({
|
|
116
|
-
userName,
|
|
117
|
-
strategyName,
|
|
118
|
-
config: release.config,
|
|
119
|
-
baseReleaseVersion: release.releaseVersion,
|
|
120
|
-
updatedBy: userName,
|
|
121
|
-
});
|
|
122
|
-
return release;
|
|
123
|
-
};
|