text-runner-engine 7.1.0

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 (176) hide show
  1. package/README.md +36 -0
  2. package/dist/actions/actions.d.ts +11 -0
  3. package/dist/actions/actions.js +36 -0
  4. package/dist/actions/built-in/check-image.d.ts +4 -0
  5. package/dist/actions/built-in/check-image.js +59 -0
  6. package/dist/actions/built-in/check-link.d.ts +4 -0
  7. package/dist/actions/built-in/check-link.js +127 -0
  8. package/dist/actions/built-in/test.d.ts +3 -0
  9. package/dist/actions/built-in/test.js +4 -0
  10. package/dist/actions/export.d.ts +18 -0
  11. package/dist/actions/export.js +2 -0
  12. package/dist/actions/external-action-manager.d.ts +10 -0
  13. package/dist/actions/external-action-manager.js +44 -0
  14. package/dist/actions/finder.d.ts +29 -0
  15. package/dist/actions/finder.js +99 -0
  16. package/dist/actions/index.d.ts +37 -0
  17. package/dist/actions/index.js +3 -0
  18. package/dist/actions/name.d.ts +2 -0
  19. package/dist/actions/name.js +6 -0
  20. package/dist/activities/extract-dynamic.d.ts +5 -0
  21. package/dist/activities/extract-dynamic.js +29 -0
  22. package/dist/activities/extract-images-and-links.d.ts +5 -0
  23. package/dist/activities/extract-images-and-links.js +32 -0
  24. package/dist/activities/index.d.ts +20 -0
  25. package/dist/activities/index.js +14 -0
  26. package/dist/activities/normalize-action-name.d.ts +3 -0
  27. package/dist/activities/normalize-action-name.js +13 -0
  28. package/dist/ast/index.d.ts +3 -0
  29. package/dist/ast/index.js +3 -0
  30. package/dist/ast/node-list.d.ts +41 -0
  31. package/dist/ast/node-list.js +128 -0
  32. package/dist/ast/node.d.ts +49 -0
  33. package/dist/ast/node.js +53 -0
  34. package/dist/commands/command.d.ts +9 -0
  35. package/dist/commands/command.js +2 -0
  36. package/dist/commands/debug.d.ts +16 -0
  37. package/dist/commands/debug.js +122 -0
  38. package/dist/commands/dynamic.d.ts +13 -0
  39. package/dist/commands/dynamic.js +57 -0
  40. package/dist/commands/index.d.ts +7 -0
  41. package/dist/commands/index.js +7 -0
  42. package/dist/commands/run.d.ts +14 -0
  43. package/dist/commands/run.js +66 -0
  44. package/dist/commands/static.d.ts +13 -0
  45. package/dist/commands/static.js +60 -0
  46. package/dist/commands/unused.d.ts +13 -0
  47. package/dist/commands/unused.js +43 -0
  48. package/dist/configuration/data.d.ts +46 -0
  49. package/dist/configuration/data.js +2 -0
  50. package/dist/configuration/defaults.d.ts +5 -0
  51. package/dist/configuration/defaults.js +50 -0
  52. package/dist/configuration/index.d.ts +5 -0
  53. package/dist/configuration/index.js +4 -0
  54. package/dist/configuration/publication.d.ts +42 -0
  55. package/dist/configuration/publication.js +51 -0
  56. package/dist/configuration/publications.d.ts +9 -0
  57. package/dist/configuration/publications.js +25 -0
  58. package/dist/errors/error.d.ts +2 -0
  59. package/dist/errors/error.js +9 -0
  60. package/dist/errors/node-error.d.ts +3 -0
  61. package/dist/errors/node-error.js +5 -0
  62. package/dist/errors/user-error.d.ts +13 -0
  63. package/dist/errors/user-error.js +16 -0
  64. package/dist/events/index.d.ts +49 -0
  65. package/dist/events/index.js +13 -0
  66. package/dist/filesystem/absolute-dir.d.ts +25 -0
  67. package/dist/filesystem/absolute-dir.js +42 -0
  68. package/dist/filesystem/absolute-file.d.ts +18 -0
  69. package/dist/filesystem/absolute-file.js +28 -0
  70. package/dist/filesystem/full-dir.d.ts +11 -0
  71. package/dist/filesystem/full-dir.js +15 -0
  72. package/dist/filesystem/full-file.d.ts +22 -0
  73. package/dist/filesystem/full-file.js +35 -0
  74. package/dist/filesystem/full-link.d.ts +40 -0
  75. package/dist/filesystem/full-link.js +80 -0
  76. package/dist/filesystem/full-path.d.ts +31 -0
  77. package/dist/filesystem/full-path.js +61 -0
  78. package/dist/filesystem/get-filenames.d.ts +15 -0
  79. package/dist/filesystem/get-filenames.js +73 -0
  80. package/dist/filesystem/has-directory.d.ts +2 -0
  81. package/dist/filesystem/has-directory.js +11 -0
  82. package/dist/filesystem/index.d.ts +15 -0
  83. package/dist/filesystem/index.js +15 -0
  84. package/dist/filesystem/is-markdown-file.d.ts +2 -0
  85. package/dist/filesystem/is-markdown-file.js +11 -0
  86. package/dist/filesystem/location.d.ts +17 -0
  87. package/dist/filesystem/location.js +26 -0
  88. package/dist/filesystem/relative-dir.d.ts +6 -0
  89. package/dist/filesystem/relative-dir.js +7 -0
  90. package/dist/filesystem/relative-link.d.ts +16 -0
  91. package/dist/filesystem/relative-link.js +18 -0
  92. package/dist/filesystem/source-dir.d.ts +19 -0
  93. package/dist/filesystem/source-dir.js +35 -0
  94. package/dist/filesystem/unknown-link.d.ts +15 -0
  95. package/dist/filesystem/unknown-link.js +23 -0
  96. package/dist/helpers/add-leading-dot-unless-empty.d.ts +2 -0
  97. package/dist/helpers/add-leading-dot-unless-empty.js +10 -0
  98. package/dist/helpers/add-leading-slash.d.ts +2 -0
  99. package/dist/helpers/add-leading-slash.js +9 -0
  100. package/dist/helpers/add-trailing-slash.d.ts +2 -0
  101. package/dist/helpers/add-trailing-slash.js +7 -0
  102. package/dist/helpers/index.d.ts +16 -0
  103. package/dist/helpers/index.js +16 -0
  104. package/dist/helpers/is-external-link.d.ts +2 -0
  105. package/dist/helpers/is-external-link.js +5 -0
  106. package/dist/helpers/is-link-to-anchor-in-other-file.d.ts +2 -0
  107. package/dist/helpers/is-link-to-anchor-in-other-file.js +4 -0
  108. package/dist/helpers/is-link-to-anchor-in-same-file.d.ts +2 -0
  109. package/dist/helpers/is-link-to-anchor-in-same-file.js +4 -0
  110. package/dist/helpers/is-mailto-link.d.ts +2 -0
  111. package/dist/helpers/is-mailto-link.js +4 -0
  112. package/dist/helpers/remove-double-slash.d.ts +3 -0
  113. package/dist/helpers/remove-double-slash.js +6 -0
  114. package/dist/helpers/remove-leading-slash.d.ts +2 -0
  115. package/dist/helpers/remove-leading-slash.js +7 -0
  116. package/dist/helpers/remove-trailing-colon.d.ts +2 -0
  117. package/dist/helpers/remove-trailing-colon.js +9 -0
  118. package/dist/helpers/straighten-link.d.ts +3 -0
  119. package/dist/helpers/straighten-link.js +15 -0
  120. package/dist/helpers/trim-all-line-ends.d.ts +3 -0
  121. package/dist/helpers/trim-all-line-ends.js +5 -0
  122. package/dist/helpers/trim-extension.d.ts +2 -0
  123. package/dist/helpers/trim-extension.js +5 -0
  124. package/dist/helpers/unixify.d.ts +3 -0
  125. package/dist/helpers/unixify.js +6 -0
  126. package/dist/link-targets/find.d.ts +4 -0
  127. package/dist/link-targets/find.js +9 -0
  128. package/dist/link-targets/index.d.ts +15 -0
  129. package/dist/link-targets/index.js +3 -0
  130. package/dist/link-targets/list.d.ts +18 -0
  131. package/dist/link-targets/list.js +74 -0
  132. package/dist/link-targets/target-url.d.ts +2 -0
  133. package/dist/link-targets/target-url.js +8 -0
  134. package/dist/parsers/html/html-parser.d.ts +28 -0
  135. package/dist/parsers/html/html-parser.js +153 -0
  136. package/dist/parsers/html/index.d.ts +2 -0
  137. package/dist/parsers/html/index.js +2 -0
  138. package/dist/parsers/html/parse-html-files.d.ts +6 -0
  139. package/dist/parsers/html/parse-html-files.js +16 -0
  140. package/dist/parsers/index.d.ts +3 -0
  141. package/dist/parsers/index.js +3 -0
  142. package/dist/parsers/markdown/closing-tag-parser.d.ts +13 -0
  143. package/dist/parsers/markdown/closing-tag-parser.js +29 -0
  144. package/dist/parsers/markdown/index.d.ts +3 -0
  145. package/dist/parsers/markdown/index.js +3 -0
  146. package/dist/parsers/markdown/md-parser.d.ts +50 -0
  147. package/dist/parsers/markdown/md-parser.js +316 -0
  148. package/dist/parsers/markdown/open-node-tracker.d.ts +14 -0
  149. package/dist/parsers/markdown/open-node-tracker.js +33 -0
  150. package/dist/parsers/markdown/parse.d.ts +5 -0
  151. package/dist/parsers/markdown/parse.js +15 -0
  152. package/dist/parsers/tag-mapper.d.ts +31 -0
  153. package/dist/parsers/tag-mapper.js +112 -0
  154. package/dist/run/activity-collector.d.ts +29 -0
  155. package/dist/run/activity-collector.js +35 -0
  156. package/dist/run/index.d.ts +9 -0
  157. package/dist/run/index.js +5 -0
  158. package/dist/run/name-refiner.d.ts +13 -0
  159. package/dist/run/name-refiner.js +20 -0
  160. package/dist/run/output-collector.d.ts +13 -0
  161. package/dist/run/output-collector.js +30 -0
  162. package/dist/run/parallel.d.ts +11 -0
  163. package/dist/run/parallel.js +13 -0
  164. package/dist/run/run-activity.d.ts +8 -0
  165. package/dist/run/run-activity.js +76 -0
  166. package/dist/run/sequential.d.ts +7 -0
  167. package/dist/run/sequential.js +10 -0
  168. package/dist/run/stopwatch.d.ts +12 -0
  169. package/dist/run/stopwatch.js +19 -0
  170. package/dist/text-runner.d.ts +15 -0
  171. package/dist/text-runner.js +15 -0
  172. package/dist/workspace/create.d.ts +4 -0
  173. package/dist/workspace/create.js +10 -0
  174. package/dist/workspace/index.d.ts +2 -0
  175. package/dist/workspace/index.js +2 -0
  176. package/package.json +45 -0
package/README.md ADDED
@@ -0,0 +1,36 @@
1
+ <!-- logo is from: https://icons8.com/icon/40886/test -->
2
+ <img src="../documentation/logo2.png" align="right" valign="bottom">
3
+
4
+ # Text-Runner
5
+
6
+ > Test runner for text
7
+
8
+ Text-Runner makes rich text executable. An example is the documentation you are
9
+ reading right now. Text-Runner executes certain parts of it to verify that it
10
+ correctly describes how Text-Runner works. Text-Runner allows you to
11
+
12
+ - test your natural-language documentation
13
+ - make your end-to-end tests completely human-readable prose
14
+ - write computer programs in natural language
15
+
16
+ There are no limitations on the type of text that TextRunner can execute. It can
17
+ read and understand (if you tell it how) any form of plain text in any human
18
+ language as well as complex data in tables, bullet point lists, or embedded
19
+ images. This can be useful for things like
20
+
21
+ - **agile documentation:** your documentation always matches the behavior of the
22
+ product it describes
23
+ - **[semantic versioning](http://semver.org):** know whether a product change
24
+ affects documented behavior
25
+ - **[readme-driven development](http://tom.preston-werner.com/2010/08/23/readme-driven-development.html):**
26
+ documentation becomes your product prototype and drives the implementation
27
+
28
+ More information about:
29
+
30
+ - [how it works](../documentation/how-it-works.md)
31
+ - [installation](../documentation/installation.md)
32
+ - [built-in actions](../documentation/built-in-actions.md)
33
+ - [user-defined actions](../documentation/user-defined-actions.md)
34
+ - [external actions](../documentation/external-actions.md)
35
+ - [questions and answers](../documentation/qna.md)
36
+ - [related tools](../documentation/related-tools.md)
@@ -0,0 +1,11 @@
1
+ import { Action, FunctionRepo } from "./index.js";
2
+ export declare class Actions {
3
+ list: FunctionRepo;
4
+ constructor();
5
+ get(name: string): Action | undefined;
6
+ /** provides the names of all registered actions */
7
+ names(): string[];
8
+ register(fileBasedName: string, action: any): void;
9
+ size(): number;
10
+ }
11
+ //# sourceMappingURL=actions.d.ts.map
@@ -0,0 +1,36 @@
1
+ import slugify from "@sindresorhus/slugify";
2
+ export class Actions {
3
+ constructor() {
4
+ this.list = {};
5
+ }
6
+ get(name) {
7
+ return this.list[name];
8
+ }
9
+ /** provides the names of all registered actions */
10
+ names() {
11
+ return Object.keys(this.list).sort();
12
+ }
13
+ register(fileBasedName, action) {
14
+ if (typeof action === "function") {
15
+ this.list[fileBasedName] = action;
16
+ }
17
+ else if (typeof action.default === "function") {
18
+ this.list[fileBasedName] = action.default;
19
+ }
20
+ else if (typeof action === "object") {
21
+ for (const key of Object.keys(action)) {
22
+ if (typeof key !== "string") {
23
+ throw new Error(`Key "${key}" has unexpected type "${typeof key}", expected string`);
24
+ }
25
+ this.list[slugify(key)] = action[key];
26
+ }
27
+ }
28
+ else {
29
+ throw new Error(`Cannot use object of type "${typeof action}" as an exported action: ${action}`);
30
+ }
31
+ }
32
+ size() {
33
+ return Object.keys(this.list).length;
34
+ }
35
+ }
36
+ //# sourceMappingURL=actions.js.map
@@ -0,0 +1,4 @@
1
+ import * as actions from "../index.js";
2
+ /** The "checkImage" action checks for broken images. */
3
+ export declare function checkImage(action: actions.Args): Promise<number | void>;
4
+ //# sourceMappingURL=check-image.d.ts.map
@@ -0,0 +1,59 @@
1
+ import { promises as fs } from "fs";
2
+ import got, { HTTPError, TimeoutError } from "got";
3
+ import * as files from "../../filesystem/index.js";
4
+ /** The "checkImage" action checks for broken images. */
5
+ export async function checkImage(action) {
6
+ const node = action.region[0];
7
+ const imagePath = node.attributes.src;
8
+ if (!imagePath) {
9
+ throw new Error("image tag without source");
10
+ }
11
+ action.name(`image ${imagePath}`);
12
+ if (isRemoteImage(imagePath)) {
13
+ const result = await checkRemoteImage(imagePath, action);
14
+ return result;
15
+ }
16
+ else {
17
+ // local image here
18
+ const imageFullFile = new files.FullFilePath(imagePath.startsWith("/") ? imagePath : node.location.file.directory().joinStr(imagePath));
19
+ const result = await checkLocalImage(imageFullFile, action.configuration);
20
+ return result;
21
+ }
22
+ }
23
+ async function checkLocalImage(image, c) {
24
+ try {
25
+ await fs.stat(c.sourceDir.joinFullFile(image).platformified());
26
+ }
27
+ catch (err) {
28
+ throw new Error(`image ${image.unixified()} does not exist`);
29
+ }
30
+ }
31
+ async function checkRemoteImage(url, action) {
32
+ if (!action.configuration.online) {
33
+ return action.SKIPPING;
34
+ }
35
+ try {
36
+ await got(url, { timeout: { request: 2000 } });
37
+ }
38
+ catch (err) {
39
+ if (err instanceof HTTPError && err.response.statusCode === 404) {
40
+ action.log(`image ${url} does not exist`);
41
+ }
42
+ else if (err instanceof TimeoutError) {
43
+ action.log(`image ${url} timed out`);
44
+ }
45
+ else {
46
+ throw err;
47
+ }
48
+ }
49
+ return;
50
+ }
51
+ function isRemoteImage(imagePath) {
52
+ if (imagePath != null) {
53
+ return imagePath.startsWith("//") || imagePath.startsWith("http://") || imagePath.startsWith("https://");
54
+ }
55
+ else {
56
+ return false;
57
+ }
58
+ }
59
+ //# sourceMappingURL=check-image.js.map
@@ -0,0 +1,4 @@
1
+ import { Args } from "../index.js";
2
+ /** The "checkLink" action checks for broken hyperlinks. */
3
+ export declare function checkLink(action: Args): Promise<Args["SKIPPING"] | void>;
4
+ //# sourceMappingURL=check-link.d.ts.map
@@ -0,0 +1,127 @@
1
+ import { promises as fs } from "fs";
2
+ import got, { HTTPError, TimeoutError } from "got";
3
+ import { errorMessage } from "../../errors/error.js";
4
+ import { UserError } from "../../errors/user-error.js";
5
+ import * as files from "../../filesystem/index.js";
6
+ import * as helpers from "../../helpers/index.js";
7
+ /** The "checkLink" action checks for broken hyperlinks. */
8
+ export async function checkLink(action) {
9
+ const target = action.region.nodeOfTypes("link_open").attributes.href;
10
+ if (target == null || target === "") {
11
+ throw new Error("link without target");
12
+ }
13
+ for (const ignoreLinkTarget of action.configuration.ignoreLinkTargets) {
14
+ if (ignoreLinkTarget.test(target)) {
15
+ return action.SKIPPING;
16
+ }
17
+ }
18
+ action.name(`link to ${target}`);
19
+ if (helpers.isMailtoLink(target)) {
20
+ return action.SKIPPING;
21
+ }
22
+ if (helpers.isLinkToAnchorInSameFile(target)) {
23
+ const result = checkLinkToAnchorInSameFile(action.location.file, target, action);
24
+ return result;
25
+ }
26
+ if (helpers.isLinkToAnchorInOtherFile(target)) {
27
+ const result = checkLinkToAnchorInOtherFile(action.location, target, action);
28
+ return result;
29
+ }
30
+ if (helpers.isExternalLink(target)) {
31
+ const result = await checkExternalLink(target, action);
32
+ return result;
33
+ }
34
+ await checkLinkToFilesystem(target, action);
35
+ return;
36
+ }
37
+ async function checkExternalLink(target, action) {
38
+ if (!action.configuration.online) {
39
+ return action.SKIPPING;
40
+ }
41
+ action.name(`link to external website ${target}`);
42
+ try {
43
+ await got(target, { timeout: { request: 2000 } });
44
+ }
45
+ catch (e) {
46
+ if (e instanceof HTTPError && e.response.statusCode === 404) {
47
+ throw new Error(`external website doesn't exist: ${target}`);
48
+ }
49
+ else if (e instanceof TimeoutError) {
50
+ action.log("timed out");
51
+ }
52
+ else {
53
+ action.log(`error while checking link to ${target}: ${errorMessage(e)}`);
54
+ }
55
+ }
56
+ return;
57
+ }
58
+ function checkLinkToAnchorInOtherFile(containingLocation, target, action) {
59
+ const link = new files.UnknownLink(target);
60
+ const absoluteLink = link.absolutify(containingLocation, action.configuration.publications);
61
+ const anchorName = absoluteLink.anchor();
62
+ const fullPath = absoluteLink.localize(action.configuration.publications, action.configuration.defaultFile);
63
+ try {
64
+ var fullFile = fullPath.toFullFilePath();
65
+ }
66
+ catch (e) {
67
+ throw new Error(`link to non-existing file ${fullPath.unixified()}`);
68
+ }
69
+ if (!action.linkTargets.hasFile(fullFile)) {
70
+ throw new Error(`link to anchor #${anchorName} in non-existing file ${helpers.removeLeadingSlash(fullFile.unixified())}`);
71
+ }
72
+ if (!action.linkTargets.hasAnchor(fullFile, anchorName)) {
73
+ throw new UserError(`link to non-existing anchor ${"#" + anchorName} in ${fullFile.unixified()}`, `File ${fullFile.unixified()} contains these anchors: ${action.linkTargets
74
+ .getAnchors(fullFile)
75
+ .map(anchor => `#${anchor}`)
76
+ .join(", ")}`);
77
+ }
78
+ if (action.linkTargets.anchorType(fullFile, anchorName) === "heading") {
79
+ action.name(`link to heading ${fullFile.unixified() + "#" + anchorName}`);
80
+ }
81
+ else {
82
+ action.name(`link to ${fullFile.unixified()}#${anchorName}`);
83
+ }
84
+ }
85
+ function checkLinkToAnchorInSameFile(file, target, action) {
86
+ const anchorName = target.substr(1);
87
+ if (!action.linkTargets.hasAnchor(file, anchorName)) {
88
+ throw new UserError(`link to non-existing local anchor ${target}`, `These local anchors exist: ${action.linkTargets
89
+ .getAnchors(file)
90
+ .map(anchor => `#${anchor}`)
91
+ .join(", ")}`, action.location);
92
+ }
93
+ if (action.linkTargets.anchorType(file, anchorName) === "heading") {
94
+ action.name(`link to local heading ${target}`);
95
+ }
96
+ else {
97
+ action.name(`link to #${anchorName}`);
98
+ }
99
+ }
100
+ async function checkLinkToFilesystem(target, action) {
101
+ const unknownLink = new files.UnknownLink(decodeURI(target));
102
+ const absoluteLink = unknownLink.absolutify(action.location, action.configuration.publications);
103
+ const linkedFile = absoluteLink.localize(action.configuration.publications, action.configuration.defaultFile);
104
+ const fullPath = action.configuration.sourceDir.joinStr(linkedFile.unixified());
105
+ // We only check for directories if no defaultFile is set.
106
+ // Otherwise links to folders point to the default file.
107
+ if (!action.configuration.defaultFile) {
108
+ try {
109
+ const stats = await fs.stat(fullPath);
110
+ if (stats.isDirectory()) {
111
+ action.name(`link to local directory ${linkedFile.unixified()}`);
112
+ return;
113
+ }
114
+ }
115
+ catch (e) {
116
+ // we can ignore errors here since we keep checking the file below
117
+ }
118
+ }
119
+ action.name(`link to local file ${linkedFile.unixified()}`);
120
+ try {
121
+ await fs.stat(fullPath);
122
+ }
123
+ catch (err) {
124
+ throw new Error(`link to non-existing local file ${linkedFile.unixified()}`);
125
+ }
126
+ }
127
+ //# sourceMappingURL=check-link.js.map
@@ -0,0 +1,3 @@
1
+ import { Args } from "../index.js";
2
+ export declare function test(action: Args): void;
3
+ //# sourceMappingURL=test.d.ts.map
@@ -0,0 +1,4 @@
1
+ export function test(action) {
2
+ action.log(action.region.text());
3
+ }
4
+ //# sourceMappingURL=test.js.map
@@ -0,0 +1,18 @@
1
+ import * as actions from "./index.js";
2
+ export type Action = CbAction | PromiseAction | SyncAction;
3
+ export type CbAction = (action: actions.Args, done: DoneFunction) => void;
4
+ /** continuous-passing-style callback function */
5
+ export type DoneFunction = (err?: Error) => void;
6
+ /** expected file structure of "index.js" files exporting Text-Runner actions */
7
+ export interface IndexFile {
8
+ textrunActions: TextrunActions;
9
+ }
10
+ /** elements of "package.json" files used here */
11
+ export interface PackageJson {
12
+ exports: string;
13
+ }
14
+ export type PromiseAction = (action: actions.Args) => Promise<void>;
15
+ export type SyncAction = (action: actions.Args) => void;
16
+ /** data format of exported actions by npm modules */
17
+ export type TextrunActions = Record<string, Action>;
18
+ //# sourceMappingURL=export.d.ts.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=export.js.map
@@ -0,0 +1,10 @@
1
+ import { Activity } from "../activities/index.js";
2
+ import { Action, FunctionRepo } from "./index.js";
3
+ /** ExternalActionManager provides external actions from their own NPM modules */
4
+ export declare class ExternalActionManager {
5
+ actions: FunctionRepo;
6
+ constructor();
7
+ /** provides the external action with the given name */
8
+ get(activity: Activity): Promise<Action | null>;
9
+ }
10
+ //# sourceMappingURL=external-action-manager.d.ts.map
@@ -0,0 +1,44 @@
1
+ import * as actions from "../actions/index.js";
2
+ import { UserError } from "../errors/user-error.js";
3
+ /** ExternalActionManager provides external actions from their own NPM modules */
4
+ export class ExternalActionManager {
5
+ constructor() {
6
+ this.actions = {};
7
+ }
8
+ /** provides the external action with the given name */
9
+ async get(activity) {
10
+ const parts = activity.actionName.split("/");
11
+ if (parts.length === 1) {
12
+ // not an external action here --> ignore
13
+ return null;
14
+ }
15
+ if (parts.length > 2) {
16
+ throw new UserError(`Too many slashes in action name "${activity.actionName}"`, "Activities are only allowed to have one slash in them: to separate the npm module name from the action name", activity.location);
17
+ }
18
+ const moduleName = "textrun-" + parts[0];
19
+ const wantAction = actions.name(parts[1]);
20
+ try {
21
+ var module = await import(moduleName);
22
+ }
23
+ catch (e) {
24
+ if (!(e instanceof Error)) {
25
+ throw e;
26
+ }
27
+ throw new UserError(`cannot load npm package "${moduleName}"`, e.message, activity.location);
28
+ }
29
+ const moduleActions = module.textrunActions;
30
+ if (!moduleActions) {
31
+ throw new UserError(`npm package "${moduleName}" does not contain any Text-Runner actions`, "", activity.location);
32
+ }
33
+ const names = [];
34
+ for (const [rawName, action] of Object.entries(moduleActions)) {
35
+ const name = actions.name(rawName);
36
+ if (name === wantAction) {
37
+ return action;
38
+ }
39
+ names.push(name);
40
+ }
41
+ throw new UserError(`npm package "${moduleName}" does not contain action "${wantAction}"`, `Found actions: ${names.join(", ")}`, activity.location);
42
+ }
43
+ }
44
+ //# sourceMappingURL=external-action-manager.js.map
@@ -0,0 +1,29 @@
1
+ import * as activities from "../activities/index.js";
2
+ import * as files from "../filesystem/index.js";
3
+ import { Actions } from "./actions.js";
4
+ import { ExternalActionManager } from "./external-action-manager.js";
5
+ import { Action } from "./index.js";
6
+ /** ActionFinder provides runnable action instances for activities. */
7
+ export declare class Finder {
8
+ private readonly builtinActions;
9
+ private readonly customActions;
10
+ private readonly externalActions;
11
+ constructor(builtIn: Actions, custom: Actions, external: ExternalActionManager);
12
+ /** loads all actions */
13
+ static load(sourceDir: files.SourceDir): Promise<Finder>;
14
+ /** loads only the actions for dynamic tests */
15
+ static loadDynamic(sourceDir: files.SourceDir): Promise<Finder>;
16
+ /** loads only the actions for static tests */
17
+ static loadStatic(): Promise<Finder>;
18
+ /** actionFor provides the action function for the given Activity. */
19
+ actionFor(activity: activities.Activity): Promise<Action>;
20
+ /** customActionNames returns the names of all built-in actions. */
21
+ customActionNames(): string[];
22
+ /** errorUnknownAction signals that the given activity has no known action. */
23
+ private errorUnknownAction;
24
+ }
25
+ export declare function builtinActionFilePaths(): Promise<string[]>;
26
+ export declare function customActionFilePaths(dir: string): Promise<string[]>;
27
+ export declare function loadBuiltinActions(): Promise<Actions>;
28
+ export declare function loadCustomActions(dir: string): Promise<Actions>;
29
+ //# sourceMappingURL=finder.d.ts.map
@@ -0,0 +1,99 @@
1
+ import { promises as fs } from "fs";
2
+ import path from "path";
3
+ import * as url from "url";
4
+ import * as actions from "../actions/index.js";
5
+ import { UserError } from "../errors/user-error.js";
6
+ import { Actions } from "./actions.js";
7
+ import { ExternalActionManager } from "./external-action-manager.js";
8
+ const __dirname = url.fileURLToPath(new URL(".", import.meta.url));
9
+ /** ActionFinder provides runnable action instances for activities. */
10
+ export class Finder {
11
+ constructor(builtIn, custom, external) {
12
+ this.builtinActions = builtIn;
13
+ this.customActions = custom;
14
+ this.externalActions = external;
15
+ }
16
+ /** loads all actions */
17
+ static async load(sourceDir) {
18
+ return new Finder(await loadBuiltinActions(), await loadCustomActions(sourceDir.joinStr("text-runner")), new ExternalActionManager());
19
+ }
20
+ /** loads only the actions for dynamic tests */
21
+ static async loadDynamic(sourceDir) {
22
+ return new Finder(new Actions(), await loadCustomActions(sourceDir.joinStr("text-runner")), new ExternalActionManager());
23
+ }
24
+ /** loads only the actions for static tests */
25
+ static async loadStatic() {
26
+ return new Finder(await loadBuiltinActions(), new Actions(), new ExternalActionManager());
27
+ }
28
+ /** actionFor provides the action function for the given Activity. */
29
+ async actionFor(activity) {
30
+ return (this.builtinActions.get(activity.actionName)
31
+ || this.customActions.get(activity.actionName)
32
+ || (await this.externalActions.get(activity))
33
+ || this.errorUnknownAction(activity));
34
+ }
35
+ /** customActionNames returns the names of all built-in actions. */
36
+ customActionNames() {
37
+ return this.customActions.names();
38
+ }
39
+ /** errorUnknownAction signals that the given activity has no known action. */
40
+ errorUnknownAction(activity) {
41
+ const errorText = `unknown action: ${activity.actionName}`;
42
+ let guidance = "";
43
+ if (this.customActions.size() > 0) {
44
+ guidance += "User-defined actions:\n";
45
+ for (const actionName of this.customActions.names()) {
46
+ guidance += `* ${actionName}\n`;
47
+ }
48
+ }
49
+ else {
50
+ guidance += "No custom actions defined.\n";
51
+ }
52
+ guidance += `\nTo create a new "${activity.actionName}" action,\n`;
53
+ guidance += `run "text-runner scaffold ${activity.actionName}"\n`;
54
+ throw new UserError(errorText, guidance, activity.location);
55
+ }
56
+ }
57
+ export async function builtinActionFilePaths() {
58
+ const result = [];
59
+ const builtinDir = path.join(__dirname, "built-in");
60
+ for (const file of await fs.readdir(builtinDir)) {
61
+ if (file.endsWith(".js") || (file.endsWith(".ts") && !file.endsWith(".d.ts"))) {
62
+ result.push(path.join(builtinDir, file));
63
+ }
64
+ }
65
+ return result;
66
+ }
67
+ export async function customActionFilePaths(dir) {
68
+ try {
69
+ var files = await fs.readdir(dir);
70
+ }
71
+ catch (e) {
72
+ // it's okay if there is no dir with custom actions
73
+ return [];
74
+ }
75
+ const result = [];
76
+ for (const file of files) {
77
+ if (file.endsWith(".js") || file.endsWith(".ts")) {
78
+ result.push(path.join(dir, file));
79
+ }
80
+ }
81
+ return result;
82
+ }
83
+ export async function loadBuiltinActions() {
84
+ const result = new Actions();
85
+ for (const filename of await builtinActionFilePaths()) {
86
+ const fileURL = url.pathToFileURL(filename);
87
+ result.register(actions.name(filename), await import(fileURL.href));
88
+ }
89
+ return result;
90
+ }
91
+ export async function loadCustomActions(dir) {
92
+ const result = new Actions();
93
+ for (const filename of await customActionFilePaths(dir)) {
94
+ const fileURL = url.pathToFileURL(filename);
95
+ result.register(actions.name(filename), await import(fileURL.href));
96
+ }
97
+ return result;
98
+ }
99
+ //# sourceMappingURL=finder.js.map
@@ -0,0 +1,37 @@
1
+ import * as ast from "../ast/index.js";
2
+ import * as config from "../configuration/index.js";
3
+ import * as files from "../filesystem/index.js";
4
+ import * as linkTargets from "../link-targets/index.js";
5
+ import * as run from "../run/index.js";
6
+ export { Finder } from "./finder.js";
7
+ export * from "./name.js";
8
+ /** A user-defined or built-in function that tests an active block */
9
+ export type Action = (params: Args) => Result;
10
+ export interface Args {
11
+ /** TextRunner configuration data derived from the config file and CLI switches */
12
+ readonly configuration: config.Data;
13
+ /** the AST nodes of the active region which the current action tests */
14
+ readonly document: ast.NodeList;
15
+ /** all link targets in the current documentation */
16
+ readonly linkTargets: linkTargets.List;
17
+ /** name of the file in which the currently tested active region is */
18
+ readonly location: files.Location;
19
+ /** allows printing test output to the user, behaves like console.log */
20
+ readonly log: run.LogFn;
21
+ /**
22
+ * Name allows to provide a more specific name for the current action.
23
+ *
24
+ * As an example, the generic step name `write file` could be refined to
25
+ * `write file "foo.yml"` once the name of the file to be written
26
+ * has been extracted from the document AST.
27
+ */
28
+ readonly name: run.RefineNameFn;
29
+ /** the AST nodes of the active region which the current action tests */
30
+ readonly region: ast.NodeList;
31
+ /** return the action with this value to signal that it is being skipped */
32
+ readonly SKIPPING: 254;
33
+ }
34
+ export type FunctionRepo = Record<string, Action>;
35
+ /** the result of an action function */
36
+ export type Result = 254 | undefined;
37
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,3 @@
1
+ export { Finder } from "./finder.js";
2
+ export * from "./name.js";
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,2 @@
1
+ export declare function name(filepath: string): string;
2
+ //# sourceMappingURL=name.d.ts.map
@@ -0,0 +1,6 @@
1
+ import slugify from "@sindresorhus/slugify";
2
+ import * as path from "path";
3
+ export function name(filepath) {
4
+ return slugify(path.basename(filepath, path.extname(filepath)));
5
+ }
6
+ //# sourceMappingURL=name.js.map
@@ -0,0 +1,5 @@
1
+ import * as ast from "../ast/index.js";
2
+ import { List } from "./index.js";
3
+ /** returns all activities found in the given ast.NodeLists */
4
+ export declare function extractDynamic(docs: ast.NodeList[], regionMarker: string): List;
5
+ //# sourceMappingURL=extract-dynamic.d.ts.map
@@ -0,0 +1,29 @@
1
+ import { normalizeActionName } from "./normalize-action-name.js";
2
+ /** returns all activities found in the given ast.NodeLists */
3
+ export function extractDynamic(docs, regionMarker) {
4
+ const result = [];
5
+ for (const doc of docs) {
6
+ result.push(...extractFromAST(doc, regionMarker));
7
+ }
8
+ return result;
9
+ }
10
+ /** returns the activities contained in the given NodeList */
11
+ function extractFromAST(doc, regionMarker) {
12
+ const result = [];
13
+ for (const node of doc) {
14
+ if (isActiveBlockTag(node, regionMarker)) {
15
+ result.push({
16
+ actionName: normalizeActionName(node.attributes[regionMarker], node.location),
17
+ document: doc,
18
+ location: node.location,
19
+ region: doc.nodesFor(node)
20
+ });
21
+ }
22
+ }
23
+ return result;
24
+ }
25
+ /** returns whether the given AstNode marks an active region in the document */
26
+ function isActiveBlockTag(node, activeAttributeName) {
27
+ return !!node.attributes[activeAttributeName];
28
+ }
29
+ //# sourceMappingURL=extract-dynamic.js.map
@@ -0,0 +1,5 @@
1
+ import * as ast from "../ast/index.js";
2
+ import { List } from "./index.js";
3
+ /** extracts activities that check images and links from the given ActivityLists */
4
+ export declare function extractImagesAndLinks(ASTs: ast.NodeList[]): List;
5
+ //# sourceMappingURL=extract-images-and-links.d.ts.map
@@ -0,0 +1,32 @@
1
+ import * as ast from "../ast/index.js";
2
+ /** extracts activities that check images and links from the given ActivityLists */
3
+ export function extractImagesAndLinks(ASTs) {
4
+ const result = [];
5
+ for (const AST of ASTs) {
6
+ for (const node of AST) {
7
+ switch (node.type) {
8
+ case "image": {
9
+ const nodes = new ast.NodeList();
10
+ nodes.push(node);
11
+ result.push({
12
+ actionName: "check-image",
13
+ document: AST,
14
+ location: node.location,
15
+ region: nodes
16
+ });
17
+ break;
18
+ }
19
+ case "link_open":
20
+ result.push({
21
+ actionName: "check-link",
22
+ document: AST,
23
+ location: node.location,
24
+ region: AST.nodesFor(node)
25
+ });
26
+ break;
27
+ }
28
+ }
29
+ }
30
+ return result;
31
+ }
32
+ //# sourceMappingURL=extract-images-and-links.js.map
@@ -0,0 +1,20 @@
1
+ import * as ast from "../ast/index.js";
2
+ import * as files from "../filesystem/index.js";
3
+ export { extractDynamic } from "./extract-dynamic.js";
4
+ export { extractImagesAndLinks } from "./extract-images-and-links.js";
5
+ /**
6
+ * Activity is an action instance.
7
+ * A particular action that we are going to perform
8
+ * on a particular region of a particular document.
9
+ */
10
+ export interface Activity {
11
+ readonly actionName: string;
12
+ document: ast.NodeList;
13
+ readonly location: files.Location;
14
+ readonly region: ast.NodeList;
15
+ }
16
+ /** a list of activities */
17
+ export type List = Activity[];
18
+ /** scaffoldActivity creates a test Activity from the given data */
19
+ export declare function scaffold(data?: Partial<Activity>): Activity;
20
+ //# sourceMappingURL=index.d.ts.map