@appium/docutils 0.1.6 → 0.2.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.
Files changed (214) hide show
  1. package/README.md +2 -5
  2. package/build/lib/build/mkdocs.d.ts +58 -0
  3. package/build/lib/build/mkdocs.d.ts.map +1 -0
  4. package/build/lib/build/mkdocs.js +80 -0
  5. package/build/lib/build/mkdocs.js.map +1 -0
  6. package/build/lib/build/typedoc.d.ts +55 -0
  7. package/build/lib/build/typedoc.d.ts.map +1 -0
  8. package/build/lib/build/typedoc.js +120 -0
  9. package/build/lib/build/typedoc.js.map +1 -0
  10. package/build/lib/build-api.d.ts +18 -0
  11. package/build/lib/build-api.d.ts.map +1 -0
  12. package/build/lib/build-api.js +75 -0
  13. package/build/lib/build-api.js.map +1 -0
  14. package/build/lib/build.d.ts +21 -0
  15. package/build/lib/build.d.ts.map +1 -0
  16. package/build/lib/build.js +71 -0
  17. package/build/lib/build.js.map +1 -0
  18. package/build/lib/builder/deploy.d.ts +89 -0
  19. package/build/lib/builder/deploy.d.ts.map +1 -0
  20. package/build/lib/builder/deploy.js +105 -0
  21. package/build/lib/builder/deploy.js.map +1 -0
  22. package/build/lib/builder/index.d.ts +5 -0
  23. package/build/lib/builder/index.d.ts.map +1 -0
  24. package/build/lib/builder/index.js +21 -0
  25. package/build/lib/builder/index.js.map +1 -0
  26. package/build/lib/builder/nav.d.ts +81 -0
  27. package/build/lib/builder/nav.d.ts.map +1 -0
  28. package/build/lib/builder/nav.js +280 -0
  29. package/build/lib/builder/nav.js.map +1 -0
  30. package/build/lib/builder/reference.d.ts +57 -0
  31. package/build/lib/builder/reference.d.ts.map +1 -0
  32. package/build/lib/builder/reference.js +129 -0
  33. package/build/lib/builder/reference.js.map +1 -0
  34. package/build/lib/builder/site.d.ts +55 -0
  35. package/build/lib/builder/site.d.ts.map +1 -0
  36. package/build/lib/builder/site.js +81 -0
  37. package/build/lib/builder/site.js.map +1 -0
  38. package/build/lib/cli/command/build.d.ts +178 -0
  39. package/build/lib/cli/command/build.d.ts.map +1 -0
  40. package/build/lib/cli/command/build.js +223 -0
  41. package/build/lib/cli/command/build.js.map +1 -0
  42. package/build/lib/cli/command/deploy.d.ts +1 -0
  43. package/build/lib/cli/command/deploy.d.ts.map +1 -0
  44. package/build/lib/cli/command/deploy.js +2 -0
  45. package/build/lib/cli/command/deploy.js.map +1 -0
  46. package/build/lib/cli/command/index.d.ts +4 -0
  47. package/build/lib/cli/command/index.d.ts.map +1 -0
  48. package/build/lib/cli/command/index.js +13 -0
  49. package/build/lib/cli/command/index.js.map +1 -0
  50. package/build/lib/cli/command/init.d.ts +143 -0
  51. package/build/lib/cli/command/init.d.ts.map +1 -0
  52. package/build/lib/cli/command/init.js +164 -0
  53. package/build/lib/cli/command/init.js.map +1 -0
  54. package/build/lib/cli/command/validate.d.ts +76 -0
  55. package/build/lib/cli/command/validate.d.ts.map +1 -0
  56. package/build/lib/cli/command/validate.js +115 -0
  57. package/build/lib/cli/command/validate.js.map +1 -0
  58. package/build/lib/cli/command-init.d.ts +143 -0
  59. package/build/lib/cli/command-init.d.ts.map +1 -0
  60. package/build/lib/cli/command-init.js +164 -0
  61. package/build/lib/cli/command-init.js.map +1 -0
  62. package/build/lib/cli/command-validate.d.ts +52 -0
  63. package/build/lib/cli/command-validate.d.ts.map +1 -0
  64. package/build/lib/cli/command-validate.js +66 -0
  65. package/build/lib/cli/command-validate.js.map +1 -0
  66. package/build/lib/cli/config.d.ts +28 -0
  67. package/build/lib/cli/config.d.ts.map +1 -0
  68. package/build/lib/cli/config.js +114 -0
  69. package/build/lib/cli/config.js.map +1 -0
  70. package/build/lib/cli/index.d.ts +13 -0
  71. package/build/lib/cli/index.d.ts.map +1 -0
  72. package/build/lib/cli/index.js +91 -0
  73. package/build/lib/cli/index.js.map +1 -0
  74. package/build/lib/cli/init.d.ts +143 -0
  75. package/build/lib/cli/init.d.ts.map +1 -0
  76. package/build/lib/cli/init.js +164 -0
  77. package/build/lib/cli/init.js.map +1 -0
  78. package/build/lib/cli/options.d.ts +1 -0
  79. package/build/lib/cli/options.d.ts.map +1 -0
  80. package/build/lib/cli/options.js +2 -0
  81. package/build/lib/cli/options.js.map +1 -0
  82. package/build/lib/cli/validate.d.ts +1 -0
  83. package/build/lib/cli/validate.d.ts.map +1 -0
  84. package/build/lib/cli/validate.js +2 -0
  85. package/build/lib/cli/validate.js.map +1 -0
  86. package/build/lib/cli.d.ts +10 -0
  87. package/build/lib/cli.d.ts.map +1 -0
  88. package/build/lib/cli.js +328 -0
  89. package/build/lib/cli.js.map +1 -0
  90. package/build/lib/constants.d.ts +125 -0
  91. package/build/lib/constants.d.ts.map +1 -0
  92. package/build/lib/constants.js +133 -0
  93. package/build/lib/constants.js.map +1 -0
  94. package/build/lib/error.d.ts +3 -0
  95. package/build/lib/error.d.ts.map +1 -0
  96. package/build/lib/error.js +7 -0
  97. package/build/lib/error.js.map +1 -0
  98. package/build/lib/fs.d.ts +142 -0
  99. package/build/lib/fs.d.ts.map +1 -0
  100. package/build/lib/fs.js +237 -0
  101. package/build/lib/fs.js.map +1 -0
  102. package/build/lib/index.d.ts +5 -2
  103. package/build/lib/index.d.ts.map +1 -1
  104. package/build/lib/index.js +4 -1
  105. package/build/lib/index.js.map +1 -1
  106. package/build/lib/init-task.d.ts +49 -0
  107. package/build/lib/init-task.d.ts.map +1 -0
  108. package/build/lib/init-task.js +95 -0
  109. package/build/lib/init-task.js.map +1 -0
  110. package/build/lib/init.d.ts +202 -0
  111. package/build/lib/init.d.ts.map +1 -0
  112. package/build/lib/init.js +225 -0
  113. package/build/lib/init.js.map +1 -0
  114. package/build/lib/io.d.ts +1 -0
  115. package/build/lib/io.d.ts.map +1 -0
  116. package/build/lib/io.js +2 -0
  117. package/build/lib/io.js.map +1 -0
  118. package/build/lib/logger.d.ts +17 -2
  119. package/build/lib/logger.d.ts.map +1 -1
  120. package/build/lib/logger.js +187 -2
  121. package/build/lib/logger.js.map +1 -1
  122. package/build/lib/mike.d.ts +3 -0
  123. package/build/lib/mike.d.ts.map +1 -1
  124. package/build/lib/mike.js +4 -0
  125. package/build/lib/mike.js.map +1 -1
  126. package/build/lib/mkdocs.d.ts +51 -12
  127. package/build/lib/mkdocs.d.ts.map +1 -1
  128. package/build/lib/mkdocs.js +64 -32
  129. package/build/lib/mkdocs.js.map +1 -1
  130. package/build/lib/model.d.ts +80 -0
  131. package/build/lib/model.d.ts.map +1 -0
  132. package/build/lib/model.js +8 -0
  133. package/build/lib/model.js.map +1 -0
  134. package/build/lib/nav.d.ts +47 -0
  135. package/build/lib/nav.d.ts.map +1 -0
  136. package/build/lib/nav.js +132 -0
  137. package/build/lib/nav.js.map +1 -0
  138. package/build/lib/scaffold.d.ts +95 -0
  139. package/build/lib/scaffold.d.ts.map +1 -0
  140. package/build/lib/scaffold.js +103 -0
  141. package/build/lib/scaffold.js.map +1 -0
  142. package/build/lib/test.d.ts +9 -0
  143. package/build/lib/test.d.ts.map +1 -0
  144. package/build/lib/test.js +2 -0
  145. package/build/lib/test.js.map +1 -0
  146. package/build/lib/typedoc.d.ts +55 -0
  147. package/build/lib/typedoc.d.ts.map +1 -0
  148. package/build/lib/typedoc.js +122 -0
  149. package/build/lib/typedoc.js.map +1 -0
  150. package/build/lib/types.d.ts +52 -0
  151. package/build/lib/types.d.ts.map +1 -0
  152. package/build/lib/types.js +7 -0
  153. package/build/lib/types.js.map +1 -0
  154. package/build/lib/util.d.ts +42 -0
  155. package/build/lib/util.d.ts.map +1 -0
  156. package/build/lib/util.js +56 -0
  157. package/build/lib/util.js.map +1 -0
  158. package/build/lib/validate.d.ts +218 -0
  159. package/build/lib/validate.d.ts.map +1 -0
  160. package/build/lib/validate.js +501 -0
  161. package/build/lib/validate.js.map +1 -0
  162. package/build/lib/validation/base-validator.d.ts +218 -0
  163. package/build/lib/validation/base-validator.d.ts.map +1 -0
  164. package/build/lib/validation/base-validator.js +453 -0
  165. package/build/lib/validation/base-validator.js.map +1 -0
  166. package/build/lib/validation/mkdocs-validator.d.ts +5 -0
  167. package/build/lib/validation/mkdocs-validator.d.ts.map +1 -0
  168. package/build/lib/validation/mkdocs-validator.js +54 -0
  169. package/build/lib/validation/mkdocs-validator.js.map +1 -0
  170. package/build/lib/validation/python-validator.d.ts +1 -0
  171. package/build/lib/validation/python-validator.d.ts.map +1 -0
  172. package/build/lib/validation/python-validator.js +2 -0
  173. package/build/lib/validation/python-validator.js.map +1 -0
  174. package/build/lib/validation/python.d.ts +1 -0
  175. package/build/lib/validation/python.d.ts.map +1 -0
  176. package/build/lib/validation/python.js +2 -0
  177. package/build/lib/validation/python.js.map +1 -0
  178. package/build/lib/validation/validate.d.ts +221 -0
  179. package/build/lib/validation/validate.d.ts.map +1 -0
  180. package/build/lib/validation/validate.js +508 -0
  181. package/build/lib/validation/validate.js.map +1 -0
  182. package/build/lib/validation/validator.d.ts +220 -0
  183. package/build/lib/validation/validator.d.ts.map +1 -0
  184. package/build/lib/validation/validator.js +470 -0
  185. package/build/lib/validation/validator.js.map +1 -0
  186. package/lib/builder/deploy.ts +223 -0
  187. package/lib/builder/index.ts +4 -0
  188. package/lib/builder/nav.ts +399 -0
  189. package/lib/builder/reference.ts +191 -0
  190. package/lib/builder/site.ts +143 -0
  191. package/lib/cli/command/build.ts +229 -0
  192. package/lib/cli/command/index.ts +3 -0
  193. package/lib/cli/command/init.ts +166 -0
  194. package/lib/cli/command/validate.ts +122 -0
  195. package/lib/cli/config.ts +89 -0
  196. package/lib/cli/index.ts +88 -0
  197. package/lib/constants.ts +150 -0
  198. package/lib/error.ts +1 -0
  199. package/lib/fs.ts +274 -0
  200. package/lib/index.ts +5 -0
  201. package/lib/init.ts +319 -0
  202. package/lib/logger.ts +198 -0
  203. package/lib/mike.js +4 -0
  204. package/lib/model.ts +92 -0
  205. package/lib/scaffold.ts +225 -0
  206. package/lib/util.ts +76 -0
  207. package/lib/validate.ts +728 -0
  208. package/package.json +38 -6
  209. package/requirements.txt +4 -0
  210. package/tsconfig.json +2 -1
  211. package/build/tsconfig.tsbuildinfo +0 -1
  212. package/lib/index.js +0 -2
  213. package/lib/logger.js +0 -3
  214. package/lib/mkdocs.js +0 -43
package/lib/logger.ts ADDED
@@ -0,0 +1,198 @@
1
+ /**
2
+ * It's a logger.
3
+ *
4
+ * Since this is a CLI app only, it doesn't necessarily make sense to consume `@appium/support`'s logger.
5
+ *
6
+ * @module
7
+ */
8
+
9
+ import figures from 'figures';
10
+ import logSymbols from 'log-symbols';
11
+ import chalk, {ForegroundColor, BackgroundColor} from 'chalk';
12
+ import consola, {
13
+ logType as LogType,
14
+ ConsolaReporterLogObject,
15
+ FancyReporter,
16
+ FancyReporterOptions,
17
+ Consola,
18
+ ConsolaOptions,
19
+ LogLevel,
20
+ } from 'consola';
21
+ import {DEFAULT_LOG_LEVEL, LogLevelMap} from './constants';
22
+ import _ from 'lodash';
23
+
24
+ /**
25
+ * This is a reporter for `consola` which uses some extra/custom icons and colors.
26
+ *
27
+ * @privateRemarks
28
+ * I did not like that the default `FancyReport` logs errors in _green_ without any sort of icon.
29
+ * Both `log-symbols` and `consola` consume `chalk`, so we do too. `consola` also depends on `figures`.
30
+ */
31
+ class DocutilsReporter extends FancyReporter {
32
+ /**
33
+ * Mapping of log types (the name of the logging method called) to chalk fg colors
34
+ */
35
+ static TYPE_COLOR_MAP = {
36
+ info: 'cyan',
37
+ success: 'green',
38
+ error: 'red',
39
+ warn: 'yellow',
40
+ } as const;
41
+
42
+ /**
43
+ * Mapping of log levels to chalk fg colors
44
+ */
45
+ static LEVEL_COLORS = {
46
+ 0: 'red',
47
+ 1: 'yellow',
48
+ 2: 'white',
49
+ 3: 'green',
50
+ } as const;
51
+
52
+ /**
53
+ * Mapping of log types to icons/symbols
54
+ */
55
+ static TYPE_ICONS = {
56
+ info: logSymbols.info,
57
+ success: logSymbols.success,
58
+ error: logSymbols.error,
59
+ warn: logSymbols.warning,
60
+ debug: figures('›'),
61
+ trace: figures('›'),
62
+ } as const;
63
+
64
+ /**
65
+ * Default color to use if we can't find a color for the log type or level
66
+ */
67
+ static readonly DEFAULT_COLOR = 'grey';
68
+
69
+ /**
70
+ * Type guard to check if a log type has a color
71
+ * @param type A log type
72
+ */
73
+ static hasTypeColor(type: LogType): type is keyof typeof DocutilsReporter.TYPE_COLOR_MAP {
74
+ return type in DocutilsReporter.TYPE_COLOR_MAP;
75
+ }
76
+
77
+ /**
78
+ * Type guard to check if a log level has a color
79
+ * @param level A log level
80
+ */
81
+ static hasLevelColor(level: LogLevel): level is keyof typeof DocutilsReporter.LEVEL_COLORS {
82
+ return level in DocutilsReporter.LEVEL_COLORS;
83
+ }
84
+
85
+ /**
86
+ * Type guard to check if a log type has an icon
87
+ * @param type A log type
88
+ */
89
+ static hasTypeIcon(type: LogType): type is keyof typeof DocutilsReporter.TYPE_ICONS {
90
+ return type in DocutilsReporter.TYPE_ICONS;
91
+ }
92
+
93
+ /**
94
+ * Prefixes the logging output with colors and symbols, depending on contents of `logObj`.
95
+ * @param logObj Consola's log object
96
+ * @param isBadge {@linkcode FancyReporter} uses this; I think it depends on the terminal width
97
+ * @returns
98
+ */
99
+ protected override formatType(logObj: ConsolaReporterLogObject, isBadge?: boolean): string {
100
+ const {
101
+ TYPE_COLOR_MAP,
102
+ LEVEL_COLORS,
103
+ TYPE_ICONS,
104
+ hasTypeColor,
105
+ hasLevelColor,
106
+ hasTypeIcon,
107
+ DEFAULT_COLOR,
108
+ } = DocutilsReporter;
109
+
110
+ let typeColor: typeof ForegroundColor;
111
+ if (hasTypeColor(logObj.type)) {
112
+ typeColor = TYPE_COLOR_MAP[logObj.type];
113
+ } else if (hasLevelColor(logObj.level)) {
114
+ typeColor = LEVEL_COLORS[logObj.level];
115
+ } else {
116
+ typeColor = <typeof ForegroundColor>(
117
+ ((this.options as FancyReporterOptions).secondaryColor ?? DEFAULT_COLOR)
118
+ );
119
+ }
120
+
121
+ if (isBadge) {
122
+ return chalk[('bg' + _.capitalize(typeColor)) as typeof BackgroundColor].black(
123
+ ` ${_.toUpper(logObj.type)}`
124
+ );
125
+ }
126
+
127
+ const type = hasTypeIcon(logObj.type) ? TYPE_ICONS[logObj.type] : logObj.type;
128
+ return type ? chalk[typeColor](type) : '';
129
+ }
130
+ }
131
+
132
+ /**
133
+ * The global log level
134
+ *
135
+ * "Global" inasmuch as any logger created from the root logger will use this level.
136
+ */
137
+ let globalLevel = LogLevelMap[DEFAULT_LOG_LEVEL];
138
+
139
+ /**
140
+ * The logger from which all loggers are created. This one uses a unique tag and our custom reporter.
141
+ */
142
+ const rootLogger = createLogProxy(
143
+ consola.create({defaults: {tag: 'docutils'}, reporters: [new DocutilsReporter()]})
144
+ );
145
+
146
+ /**
147
+ * @summary Creates a log-level-propagating proxy for a {@linkcode Consola} logger.
148
+ * @description
149
+ * Alright. So when you create a new logger via {@linkcode Consola.create}, it's basically a clone
150
+ * of its parent with a new set of options.
151
+ *
152
+ * If we change the log level of the root logger (which we do: see `cli/index.ts`), we may (almost
153
+ * certainly) have
154
+ * child loggers which: a) have already been created and b) have inherited the old/default log level
155
+ * from the root logger. We don't _want_ that (though this is likely a reasonable use case) for our
156
+ * purposes.
157
+ *
158
+ * The implementation below solves the problem by maintaining its own singleton log level value, and
159
+ * intercepts the `level` property of any logger created from the root logger.
160
+ *
161
+ * There are other ways to go about this which may be better, but this seemed pretty straightforward.
162
+ */
163
+ function createLogProxy(logger: Consola): Consola {
164
+ return new Proxy(logger, {
165
+ get(target, prop, receiver) {
166
+ if (prop === 'level') {
167
+ return globalLevel;
168
+ }
169
+ if (prop === 'create') {
170
+ const create = Reflect.get(target, prop, receiver) as Consola['create'];
171
+ return (opts: ConsolaOptions) => createLogProxy(create.call(receiver, opts));
172
+ }
173
+ if (prop === '_defaults') {
174
+ const defaults = Reflect.get(target, prop, receiver);
175
+ return {...defaults, level: globalLevel};
176
+ }
177
+ return Reflect.get(target, prop, receiver);
178
+ },
179
+ set(target, prop, value, receiver) {
180
+ if (prop === 'level') {
181
+ globalLevel = value as LogLevel;
182
+ return true;
183
+ }
184
+ return Reflect.set(target, prop, value, receiver);
185
+ },
186
+ });
187
+ }
188
+
189
+ /**
190
+ * The proxied root logger
191
+ * @see {createLogProxy}
192
+ */
193
+ export default rootLogger;
194
+
195
+ // these are just type-sanity checks
196
+ <{[k in LogType]?: typeof ForegroundColor}>DocutilsReporter.TYPE_COLOR_MAP;
197
+ <{[k in LogLevel]?: typeof ForegroundColor}>DocutilsReporter.LEVEL_COLORS;
198
+ <{[k in LogType]?: string}>DocutilsReporter.TYPE_ICONS;
package/lib/mike.js CHANGED
@@ -1,10 +1,14 @@
1
1
  import {exec} from 'teen_process';
2
+ // eslint-disable-next-line import/no-unresolved
2
3
  import log from './logger';
3
4
 
4
5
  const DEFAULT_REMOTE = 'origin';
5
6
  const DEFAULT_BRANCH = 'gh-pages';
6
7
  const MIKE_VER_STRING = 'mike 1.';
7
8
 
9
+ /**
10
+ * @deprecated Use the `deploy` export from `@appium/docutils`
11
+ */
8
12
  export class Mike {
9
13
  /** @type {string} */ remote;
10
14
  /** @type {string} */ branch;
package/lib/model.ts ADDED
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Various data models, mostly referring to config files
3
+ *
4
+ * @module
5
+ */
6
+
7
+ import type {Jsonify, JsonValue, TsConfigJson as TsConfigJsonBase} from 'type-fest';
8
+ import type {TypeDocOptions} from 'typedoc';
9
+
10
+ /**
11
+ * A `tsconfig.json` file w/ `$schema` prop
12
+ *
13
+ * Due to some `unknown` types in {@linkcode type-fest.TsConfigJson}, we cannot use that type
14
+ * directly and need to use `Jsonify`.
15
+ *
16
+ */
17
+ export type TsConfigJson = Jsonify<
18
+ TsConfigJsonBase & {
19
+ $schema?: string;
20
+ }
21
+ >;
22
+
23
+ /**
24
+ * A `typedoc.json` file w/ `$schema` and `extends` props
25
+ *
26
+ * TypeDoc doesn't recognize `$schema` and ignores it; its own config parser expands the value of
27
+ * `extends` before it reaches its `Options` class. This is why we cannot use `TypeDocOptions` directly.
28
+ */
29
+ export type TypeDocJson = Jsonify<
30
+ Partial<TypeDocOptions> & {
31
+ $schema?: string;
32
+ extends?: string;
33
+ }
34
+ >;
35
+
36
+ /**
37
+ * The `nav` prop of an `mkdocs.yml` file
38
+ * @see {@linkcode MkDocsYml}
39
+ */
40
+ export type MkDocsYmlNav = Array<string | Record<string, string> | Record<string, MkDocsYmlNav>>;
41
+
42
+ /**
43
+ * This was built by hand from the MkDocs documentation
44
+ * @see https://www.mkdocs.org/user-guide/configuration/
45
+ */
46
+ export type MkDocsYml = Jsonify<{
47
+ copyright?: string;
48
+ dev_addr?: string;
49
+ docs_dir?: string;
50
+ extra_css?: string[];
51
+ extra_javascript?: string[];
52
+ extra_templates?: string[];
53
+ extra?: Record<string, JsonValue>;
54
+ hooks?: string[];
55
+ INHERIT?: string;
56
+ markdown_extensions?: Array<string | Record<string, JsonValue>>;
57
+ nav?: MkDocsYmlNav;
58
+ plugins?: Array<string | Record<string, JsonValue>>;
59
+ repo_name?: string;
60
+ repo_url?: string;
61
+ site_dir?: string;
62
+ /**
63
+ * This is _actually_ required by mkdocs
64
+ */
65
+ site_name?: string;
66
+ site_description?: string;
67
+ strict?: boolean;
68
+ theme?: MkDocsYmlTheme;
69
+ use_directory_urls?: boolean;
70
+ watch?: string[];
71
+ }>;
72
+
73
+ /**
74
+ * The `theme` prop of an `mkdocs.yml`
75
+ * @see {@linkcode MkDocsYml}
76
+ */
77
+ export type MkDocsYmlTheme =
78
+ | string
79
+ | ({
80
+ name: string;
81
+ locale?: string;
82
+ custom_dir?: string;
83
+ static_templates?: string[];
84
+ } & Record<string, JsonValue>);
85
+
86
+ /**
87
+ * The data parsed from a `requirements.txt` file, or the output of `pip list --json`
88
+ */
89
+ export interface PipPackage {
90
+ name: string;
91
+ version: string;
92
+ }
@@ -0,0 +1,225 @@
1
+ /**
2
+ * Implementation of a generic "create and/or update some file" task
3
+ * @module
4
+ */
5
+
6
+ import {fs} from '@appium/support';
7
+ import logger from './logger';
8
+ import path from 'node:path';
9
+ import {createPatch} from 'diff';
10
+ import {NormalizedPackageJson} from 'read-pkg';
11
+ import {JsonValue, JsonObject} from 'type-fest';
12
+ import {DocutilsError} from './error';
13
+ import {relative} from './util';
14
+ import _ from 'lodash';
15
+ import {stringifyJson, readPackageJson, safeWriteFile} from './fs';
16
+ import {NAME_ERR_ENOENT, NAME_ERR_EEXIST} from './constants';
17
+
18
+ const log = logger.withTag('init');
19
+ const dryRunLog = log.withTag('dry-run');
20
+
21
+ /**
22
+ * Creates a unified patch for display in "dry run" mode
23
+ * @param filename - File name to use
24
+ * @param oldData - Old data
25
+ * @param newData - New Data
26
+ * @returns Patch string
27
+ */
28
+ function makePatch<T extends JsonValue>(
29
+ filename: string,
30
+ oldData: T | string,
31
+ newData: T | string,
32
+ serializer: ScaffoldTaskSerializer<T> = stringifyJson
33
+ ) {
34
+ return createPatch(
35
+ filename,
36
+ _.isString(oldData) ? oldData : serializer(oldData),
37
+ _.isString(newData) ? newData : serializer(newData)
38
+ );
39
+ }
40
+
41
+ /**
42
+ * Options for a task which are not the {@link ScaffoldTaskOptions base options}
43
+ */
44
+ export type TaskSpecificOpts<Opts extends ScaffoldTaskOptions> = Omit<
45
+ Opts,
46
+ keyof ScaffoldTaskOptions
47
+ >;
48
+
49
+ /**
50
+ * A function which performs some scaffolding task.
51
+ *
52
+ * @see {@linkcode createScaffoldTask}
53
+ */
54
+ export type ScaffoldTask<Opts extends ScaffoldTaskOptions, T extends JsonObject> = (
55
+ opts: Opts
56
+ ) => Promise<ScaffoldTaskResult<T>>;
57
+
58
+ /**
59
+ * Factory for a {@linkcode ScaffoldTask}.
60
+ *
61
+ * @param defaultFilename Default file to create
62
+ * @param defaultContent Default content to use
63
+ * @param description Description of task
64
+ * @param opts Options
65
+ * @returns A scaffold task
66
+ */
67
+ export function createScaffoldTask<Opts extends ScaffoldTaskOptions, T extends JsonObject>(
68
+ defaultFilename: string,
69
+ defaultContent: T,
70
+ description: string,
71
+ {
72
+ transform = _.identity,
73
+ deserialize = JSON.parse,
74
+ serialize = stringifyJson,
75
+ }: CreateScaffoldTaskOptions<Opts, T> = {}
76
+ ): ScaffoldTask<Opts, T> {
77
+ return async ({
78
+ overwrite = false,
79
+ cwd = process.cwd(),
80
+ packageJson: packageJsonPath,
81
+ dest,
82
+ dryRun = false,
83
+ ...opts
84
+ }: Opts): Promise<ScaffoldTaskResult<T>> => {
85
+ const relativePath = relative(cwd);
86
+ const {pkgPath, pkg} = await readPackageJson(
87
+ packageJsonPath ? path.dirname(packageJsonPath) : cwd,
88
+ true
89
+ );
90
+ const pkgDir = path.dirname(pkgPath);
91
+ dest = dest ?? path.join(pkgDir, defaultFilename);
92
+ const relativeDest = relativePath(dest);
93
+ log.debug('Initializing %s', relativeDest);
94
+ let shouldWriteDest = false;
95
+ let destContent: T;
96
+ let result: ScaffoldTaskResult<T>;
97
+ try {
98
+ destContent = deserialize(await fs.readFile(dest, 'utf8'));
99
+ log.debug('Found existing file %s', relativeDest);
100
+ } catch (e) {
101
+ const err = e as NodeJS.ErrnoException;
102
+ if (err.code !== NAME_ERR_ENOENT) {
103
+ throw err;
104
+ }
105
+ shouldWriteDest = true;
106
+ log.debug('Creating new file %s', relativeDest);
107
+ destContent = {} as T;
108
+ }
109
+
110
+ const defaults: T = transform(defaultContent, opts, pkg);
111
+ const finalDestContent: T = _.defaultsDeep({}, destContent, defaults);
112
+
113
+ shouldWriteDest = shouldWriteDest || !_.isEqual(destContent, finalDestContent);
114
+
115
+ if (shouldWriteDest) {
116
+ log.info('Changes needed to %s', relativeDest);
117
+ log.debug('Original %s: %O', relativeDest, destContent);
118
+ log.debug('Final %s: %O', relativeDest, finalDestContent);
119
+ const patch = makePatch(dest, destContent, finalDestContent, serialize);
120
+
121
+ if (dryRun) {
122
+ dryRunLog.info('Would apply the following patch: \n\n%s', patch);
123
+ result = {path: dest, content: finalDestContent};
124
+ return result;
125
+ }
126
+
127
+ try {
128
+ await safeWriteFile(dest, finalDestContent, overwrite);
129
+ } catch (e) {
130
+ const err = e as NodeJS.ErrnoException;
131
+ // this should only be thrown if `force` is false
132
+ if (err.code === NAME_ERR_EEXIST) {
133
+ log.info(`${relativeDest} already exists; continuing...`);
134
+ log.debug(`Tried to apply patch:\n\n${patch}`);
135
+ } else {
136
+ throw new DocutilsError(`Could not write to ${relativeDest}. Reason: ${err.message}`, {
137
+ cause: err,
138
+ });
139
+ }
140
+ }
141
+ } else {
142
+ log.info('No changes to %s', relativeDest);
143
+ }
144
+ log.success('Initialized %s', description);
145
+ return {path: dest, content: finalDestContent};
146
+ };
147
+ }
148
+
149
+ /**
150
+ * Optional function which can be used to post-process the content of a file. Usually used to merge
151
+ * various options with existing content
152
+ */
153
+ export type ScaffoldTaskTransformer<Opts extends ScaffoldTaskOptions, T extends JsonValue> = (
154
+ content: Readonly<T>,
155
+ opts: TaskSpecificOpts<Opts>,
156
+ pkg: NormalizedPackageJson
157
+ ) => T;
158
+
159
+ /**
160
+ * A function which deserializes a string into a JS value.
161
+ */
162
+ export type ScaffoldTaskDeserializer<T> = (content: string) => T;
163
+
164
+ /**
165
+ * A function which serializes a JS value into a string.
166
+ */
167
+ export type ScaffoldTaskSerializer<T> = (content: T) => string;
168
+
169
+ /**
170
+ * Options for {@linkcode createScaffoldTask}
171
+ */
172
+ export interface CreateScaffoldTaskOptions<Opts extends ScaffoldTaskOptions, T extends JsonValue> {
173
+ /**
174
+ * Transformer function
175
+ */
176
+ transform?: ScaffoldTaskTransformer<Opts, T>;
177
+ /**
178
+ * Deserializer function
179
+ */
180
+ deserialize?: ScaffoldTaskDeserializer<T>;
181
+ /**
182
+ * Serializer function
183
+ */
184
+ serialize?: ScaffoldTaskSerializer<T>;
185
+ }
186
+
187
+ /**
188
+ * Base options for all scaffold tasks
189
+ */
190
+ export interface ScaffoldTaskOptions {
191
+ /**
192
+ * Current working directory
193
+ */
194
+ cwd?: string;
195
+ /**
196
+ * Destination file
197
+ */
198
+ dest?: string;
199
+ /**
200
+ * If `true` will not write files
201
+ */
202
+ dryRun?: boolean;
203
+ /**
204
+ * If `true` will overwrite fields in `typedoc.json`
205
+ */
206
+ overwrite?: boolean;
207
+ /**
208
+ * Path to `package.json`
209
+ */
210
+ packageJson?: string;
211
+ }
212
+
213
+ /**
214
+ * The return value of a {@linkcode ScaffoldTask}
215
+ */
216
+ export interface ScaffoldTaskResult<T> {
217
+ /**
218
+ * The content of whatever it wrote or would write
219
+ */
220
+ content: T;
221
+ /**
222
+ * The filepath of whatever it wrote or would write
223
+ */
224
+ path: string;
225
+ }
package/lib/util.ts ADDED
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Utilities
3
+ * @module
4
+ */
5
+
6
+ import _ from 'lodash';
7
+ import path from 'node:path';
8
+ import type {SubProcess} from 'teen_process';
9
+
10
+ /**
11
+ * Computes a relative path, prepending `./`
12
+ */
13
+ export const relative = _.curry(
14
+ (from: string, to: string): string => `.${path.sep}${path.relative(from, to)}`
15
+ );
16
+
17
+ /**
18
+ * A stopwatch-like thing
19
+ *
20
+ * Used for displaying elapsed time in milliseconds
21
+ * @param id - Unique identifier
22
+ * @returns Function that returns the elapsed time in milliseconds
23
+ */
24
+ export function stopwatch(id: string) {
25
+ const start = Date.now();
26
+ stopwatch.cache.set(id, start);
27
+ return () => {
28
+ const result = Date.now() - stopwatch.cache.get(id)!;
29
+ stopwatch.cache.delete(id);
30
+ return result;
31
+ };
32
+ }
33
+ stopwatch.cache = new Map<string, number>();
34
+
35
+ export type TupleToObject<
36
+ T extends readonly any[],
37
+ M extends Record<Exclude<keyof T, keyof any[]>, PropertyKey>
38
+ > = {[K in Exclude<keyof T, keyof any[]> as M[K]]: T[K]};
39
+
40
+ /**
41
+ * Type guard to narrow an array to a string array
42
+ * @param value any value
43
+ * @returns `true` if the array is `string[]`
44
+ */
45
+ export const isStringArray = _.overEvery(_.isArray, _.partial(_.every, _, _.isString)) as (
46
+ value: any
47
+ ) => value is string[];
48
+
49
+ /**
50
+ * Converts an object of string values to an array of arguments for CLI
51
+ *
52
+ * Supports `boolean` and `number` values as well. `boolean`s are assumed to be flags which default
53
+ * to `false`, so they will only be added to the array if the value is `true`.
54
+ */
55
+ export const argify: (obj: Record<string, string | number | boolean | undefined>) => string[] =
56
+ _.flow(
57
+ _.entries,
58
+ _.flatten,
59
+ (list) =>
60
+ list.map((value, idx) => {
61
+ if (value === true) {
62
+ return `--${value}`;
63
+ } else if (value === false || value === undefined) {
64
+ return;
65
+ }
66
+ return idx % 2 === 0 ? `--${value}` : value;
67
+ }),
68
+ _.compact
69
+ );
70
+
71
+ /**
72
+ * Conversion of the parameters of {@linkcode Subprocess.start} to an object.
73
+ */
74
+ export type TeenProcessSubprocessStartOpts = Partial<
75
+ TupleToObject<Parameters<SubProcess['start']>, ['startDetector', 'detach', 'timeoutMs']>
76
+ >;