|
| 1 | +import { throwError } from '../shared/throw' |
1 | 2 | import { AppStack } from '.' |
2 | 3 | import { AngularVersion } from './stack/angular' |
3 | 4 | import { SvelteVersion } from './stack/svelte' |
@@ -245,6 +246,114 @@ export function validateFeatures(features: Feature[], options: { stack: AppStack |
245 | 246 | } |
246 | 247 | } |
247 | 248 |
|
| 249 | +export function findFeature(name: string, pool: Feature[]) { |
| 250 | + return pool.find(f => f.name.toLocaleLowerCase() === name.toLocaleLowerCase()) |
| 251 | +} |
| 252 | + |
| 253 | +export function resolveFeatures(names: string[], pool: Feature[]) { |
| 254 | + return names.map(name => { |
| 255 | + const feature = findFeature(name, pool) |
| 256 | + |
| 257 | + if (!feature) throwError(`feature ${name} not found`) |
| 258 | + |
| 259 | + return feature |
| 260 | + }) |
| 261 | +} |
| 262 | + |
| 263 | +export function featureKeys(features: Feature[]) { |
| 264 | + return features.map(({ templateKeys }) => templateKeys ?? []).flat() |
| 265 | +} |
| 266 | + |
248 | 267 | export function getDependencies(features: Feature[]) { |
249 | | - return features.map(feature => feature.requiredDependencies || []).flat() |
| 268 | + const unique = features.filter((feature, index, list) => { |
| 269 | + return list.findIndex(item => item.name === feature.name) === index |
| 270 | + }) |
| 271 | + |
| 272 | + return unique.map(feature => feature.requiredDependencies ?? []).flat() |
| 273 | +} |
| 274 | + |
| 275 | +/** `string[]` — shared features. Object — one base `string[]` + optional extras `{ from, features }`. */ |
| 276 | +export type FeaturesInput = string[] | Record<string, string[] | { from: string, features: string[] }> |
| 277 | + |
| 278 | +export type TemplateJob = { |
| 279 | + name: string |
| 280 | + from: string |
| 281 | + features: Feature[] |
| 282 | +} |
| 283 | + |
| 284 | +function parseFeaturesObject( |
| 285 | + features: Exclude<FeaturesInput, string[]>, |
| 286 | + sources: string[] |
| 287 | +) { |
| 288 | + let base: string[] | null = null |
| 289 | + const extras: { name: string, from: string, features: string[] }[] = [] |
| 290 | + |
| 291 | + for (const [name, value] of Object.entries(features)) { |
| 292 | + if (Array.isArray(value)) { |
| 293 | + if (base) throwError('features object can contain only one base features array') |
| 294 | + base = value |
| 295 | + } else { |
| 296 | + if (sources.includes(name)) throwError(`template "${name}" already exists`) |
| 297 | + if (!sources.includes(value.from)) throwError(`template "${name}" from unknown "${value.from}"`) |
| 298 | + extras.push({ name, from: value.from, features: value.features }) |
| 299 | + } |
| 300 | + } |
| 301 | + |
| 302 | + if (!base) throwError('features object must contain a base features array') |
| 303 | + |
| 304 | + return { base, extras } |
| 305 | +} |
| 306 | + |
| 307 | +function resolveValidated( |
| 308 | + names: string[], |
| 309 | + optionalFeatures: Feature[], |
| 310 | + stack: AppStack, |
| 311 | + label?: string |
| 312 | +) { |
| 313 | + const resolved = resolveFeatures(names, optionalFeatures) |
| 314 | + const { issue } = validateFeatures(resolved, { stack }) |
| 315 | + |
| 316 | + if (issue) { |
| 317 | + throwError(label |
| 318 | + ? `template "${label}": ${issue}` |
| 319 | + : issue) |
| 320 | + } |
| 321 | + |
| 322 | + return resolved |
| 323 | +} |
| 324 | + |
| 325 | +/** Build template jobs with resolved features. */ |
| 326 | +export function resolveJobs( |
| 327 | + sources: string[], |
| 328 | + optionalFeatures: Feature[], |
| 329 | + stack: AppStack, |
| 330 | + input: { features: FeaturesInput } | { selected: Feature[] } |
| 331 | +): TemplateJob[] { |
| 332 | + if ('selected' in input) { |
| 333 | + const { issue } = validateFeatures(input.selected, { stack }) |
| 334 | + |
| 335 | + if (issue) throwError(issue) |
| 336 | + |
| 337 | + return sources.map(name => ({ name, from: name, features: input.selected })) |
| 338 | + } |
| 339 | + |
| 340 | + const { features } = input |
| 341 | + |
| 342 | + if (Array.isArray(features)) { |
| 343 | + const resolved = resolveValidated(features, optionalFeatures, stack) |
| 344 | + |
| 345 | + return sources.map(name => ({ name, from: name, features: resolved })) |
| 346 | + } |
| 347 | + |
| 348 | + const { base, extras } = parseFeaturesObject(features, sources) |
| 349 | + const shared = resolveValidated(base, optionalFeatures, stack) |
| 350 | + |
| 351 | + return [ |
| 352 | + ...sources.map(name => ({ name, from: name, features: shared })), |
| 353 | + ...extras.map(extra => ({ |
| 354 | + name: extra.name, |
| 355 | + from: extra.from, |
| 356 | + features: resolveValidated(extra.features, optionalFeatures, stack, extra.name) |
| 357 | + })) |
| 358 | + ] |
250 | 359 | } |
0 commit comments