UNPKG

4.32 kBPlain TextView Raw
1// Copyright IBM Corp. and LoopBack contributors 2018,2020. All Rights Reserved.
2// Node module: @loopback/boot
3// This file is licensed under the MIT License.
4// License text available at https://opensource.org/licenses/MIT
5
6import {Constructor} from '@loopback/core';
7import debugFactory from 'debug';
8import path from 'path';
9import {ArtifactOptions, Booter} from '../types';
10import {discoverFiles, loadClassesFromFiles} from './booter-utils';
11
12const debug = debugFactory('loopback:boot:base-artifact-booter');
13
14/**
15 * This class serves as a base class for Booters which follow a pattern of
16 * configure, discover files in a folder(s) using explicit folder / extensions
17 * or a glob pattern and lastly identifying exported classes from such files and
18 * performing an action on such files such as binding them.
19 *
20 * Any Booter extending this base class is expected to
21 *
22 * 1. Set the 'options' property to a object of ArtifactOptions type. (Each extending
23 * class should provide defaults for the ArtifactOptions and use Object.assign to merge
24 * the properties with user provided Options).
25 * 2. Provide it's own logic for 'load' after calling 'await super.load()' to
26 * actually boot the Artifact classes.
27 *
28 * Currently supports the following boot phases: configure, discover, load.
29 *
30 */
31export class BaseArtifactBooter implements Booter {
32 /**
33 * Options being used by the Booter.
34 */
35 readonly options: ArtifactOptions;
36 /**
37 * Project root relative to which all other paths are resolved
38 */
39 readonly projectRoot: string;
40 /**
41 * Relative paths of directories to be searched
42 */
43 dirs: string[];
44 /**
45 * File extensions to be searched
46 */
47 extensions: string[];
48 /**
49 * `glob` pattern to match artifact paths
50 */
51 glob: string;
52
53 /**
54 * List of files discovered by the Booter that matched artifact requirements
55 */
56 discovered: string[];
57 /**
58 * List of exported classes discovered in the files
59 */
60 classes: Constructor<{}>[];
61
62 constructor(projectRoot: string, options: ArtifactOptions) {
63 this.projectRoot = projectRoot;
64 this.options = options;
65 }
66
67 /**
68 * Get the name of the artifact loaded by this booter, e.g. "Controller".
69 * Subclasses can override the default logic based on the class name.
70 */
71 get artifactName(): string {
72 return this.constructor.name.replace(/Booter$/, '');
73 }
74
75 /**
76 * Configure the Booter by initializing the 'dirs', 'extensions' and 'glob'
77 * properties.
78 *
79 * NOTE: All properties are configured even if all aren't used.
80 */
81 async configure() {
82 this.dirs = this.options.dirs
83 ? Array.isArray(this.options.dirs)
84 ? this.options.dirs
85 : [this.options.dirs]
86 : [];
87
88 this.extensions = this.options.extensions
89 ? Array.isArray(this.options.extensions)
90 ? this.options.extensions
91 : [this.options.extensions]
92 : [];
93
94 let joinedDirs = this.dirs.join(',');
95 if (this.dirs.length > 1) joinedDirs = `{${joinedDirs}}`;
96
97 const joinedExts = `@(${this.extensions.join('|')})`;
98
99 this.glob = this.options.glob
100 ? this.options.glob
101 : `/${joinedDirs}/${this.options.nested ? '**/*' : '*'}${joinedExts}`;
102 }
103
104 /**
105 * Discover files based on the 'glob' property relative to the 'projectRoot'.
106 * Discovered artifact files matching the pattern are saved to the
107 * 'discovered' property.
108 */
109 async discover() {
110 debug(
111 'Discovering %s artifacts in %j using glob %j',
112 this.artifactName,
113 this.projectRoot,
114 this.glob,
115 );
116
117 this.discovered = await discoverFiles(this.glob, this.projectRoot);
118
119 if (debug.enabled) {
120 debug(
121 'Artifact files found: %s',
122 JSON.stringify(
123 this.discovered.map(f => path.relative(this.projectRoot, f)),
124 null,
125 2,
126 ),
127 );
128 }
129 }
130
131 /**
132 * Filters the exports of 'discovered' files to only be Classes (in case
133 * function / types are exported) as an artifact is a Class. The filtered
134 * artifact Classes are saved in the 'classes' property.
135 *
136 * NOTE: Booters extending this class should call this method (await super.load())
137 * and then process the artifact classes as appropriate.
138 */
139 async load() {
140 this.classes = loadClassesFromFiles(this.discovered, this.projectRoot);
141 }
142}