Coverage for src/lektor_ng/cli/build.py: 0%

50 statements  

« prev     ^ index     » next       coverage.py v7.15.2, created at 2026-09-05 14:56 +0000

1# pylint: disable=import-outside-toplevel 

2import os 

3import sys 

4from itertools import chain 

5from pathlib import Path 

6from lektor_ng.project import Project 

7from lektor_ng import cli_utils 

8import click 

9 

10from lektor_ng.cli_utils import ( 

11 ResolvedPath, 

12) 

13from lektor_ng.version import get_version 

14 

15 

16def add_options(fn): 

17 fn = click.argument( 

18 "project_path", 

19 type=lambda p: Path(p).expanduser().resolve(), 

20 )(fn) 

21 

22 fn = click.version_option(prog_name="Lektor", version=get_version())(fn) 

23 

24 fn = click.option( 

25 "-v", 

26 "--verbose", 

27 "verbosity", 

28 count=True, 

29 help="Increases the verbosity of the logging.", 

30 )(fn) 

31 

32 fn = cli_utils.extraflag(fn) 

33 

34 fn = click.option( 

35 "--buildstate-path", 

36 type=click.Path(writable=True, file_okay=False), 

37 default=None, 

38 help="Path to a directory that Lektor will use for coordinating " 

39 "the state of the build. Defaults to a directory named " 

40 "`.lektor` inside the output path.", 

41 )(fn) 

42 

43 fn = click.option( 

44 "--source-info-only", 

45 is_flag=True, 

46 help="Instead of building only updates the source infos. The " 

47 "source info is used by the web admin panel to quickly find " 

48 "information about the source files (for instance jump to " 

49 "files).", 

50 )(fn) 

51 

52 fn = cli_utils.pruneflag(fn) 

53 

54 fn = click.option( 

55 "--watch", 

56 is_flag=True, 

57 help="If this is enabled the build " 

58 "process goes into an automatic loop where it watches the " 

59 "file system for changes and rebuilds.", 

60 )(fn) 

61 

62 fn = click.option( 

63 "-O", 

64 "--output-path", 

65 type=ResolvedPath(writable=True, file_okay=False), 

66 default=None, 

67 help="The output path.", 

68 )(fn) 

69 

70 return fn 

71 

72 

73@click.command() 

74@add_options 

75def main( 

76 output_path, 

77 watch, 

78 prune, 

79 verbosity, 

80 source_info_only, 

81 buildstate_path, 

82 extra_flags, 

83 project_path, 

84): 

85 """Builds the entire project into the final artifacts. 

86 

87 The default behavior is to build the project into the default build 

88 output path which can be discovered with the `project-info` command 

89 but an alternative output folder can be provided with the `--output-path` 

90 option. 

91 

92 The default behavior is to perform a build followed by a pruning step 

93 which removes no longer referenced artifacts from the output folder. 

94 Lektor will only build the files that require rebuilding if the output 

95 folder is reused. 

96 

97 To enforce a clean build you have to issue a `clean` command first. 

98 

99 If the build fails the exit code will be `1` otherwise `0`. This can be 

100 used by external scripts to only deploy on successful build for instance. 

101 """ 

102 from lektor_ng.builder import Builder 

103 from lektor_ng.reporter import CliReporter 

104 

105 project = Project.from_path(project_path) 

106 

107 output_path = output_path or os.getenv("LEKTOR_BUILD_OUTPUT_PATH") or project.get_output_path() 

108 

109 from lektor_ng.environment import Environment 

110 

111 env = Environment(project, load_plugins=False) 

112 

113 # TODO re-instate load_plugins 

114 from ..pluginsystem import initialize_plugins 

115 

116 initialize_plugins(env) 

117 

118 with CliReporter(env, verbosity=verbosity): 

119 builds = ["first"] 

120 if watch: 

121 from lektor_ng.watcher import watch_project 

122 

123 click.secho("Watching for file system changes", fg="cyan") 

124 builds = chain(builds, watch_project(env, output_path, raise_interrupt=False)) 

125 

126 success = False 

127 for _ in builds: 

128 builder = Builder( 

129 env.new_pad(), 

130 output_path, 

131 buildstate_path=buildstate_path, 

132 extra_flags=extra_flags, 

133 ) 

134 if source_info_only: 

135 builder.update_all_source_infos() 

136 success = True 

137 else: 

138 failures = builder.build_all() 

139 if prune: 

140 builder.prune() 

141 success = failures == 0 

142 

143 return sys.exit(0 if success else 1) 

144 

145 

146if __name__ == "__main__": 

147 main()