@forumulate/cli 0.8.0 → 0.10.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.
- package/README.md +11 -3
- package/dist/api/args.d.ts.map +1 -1
- package/dist/api/args.js +1 -0
- package/dist/api/args.js.map +1 -1
- package/dist/api/command.d.ts +1 -1
- package/dist/api/command.d.ts.map +1 -1
- package/dist/api/command.js +86 -11
- package/dist/api/command.js.map +1 -1
- package/dist/api/http.d.ts +8 -0
- package/dist/api/http.d.ts.map +1 -1
- package/dist/api/http.js +3 -2
- package/dist/api/http.js.map +1 -1
- package/dist/api/live-spec.d.ts +48 -0
- package/dist/api/live-spec.d.ts.map +1 -0
- package/dist/api/live-spec.js +72 -0
- package/dist/api/live-spec.js.map +1 -0
- package/dist/api/operations.json +1 -1
- package/dist/api/request.d.ts.map +1 -1
- package/dist/api/request.js +25 -1
- package/dist/api/request.js.map +1 -1
- package/dist/api/send.d.ts +10 -1
- package/dist/api/send.d.ts.map +1 -1
- package/dist/api/send.js +14 -3
- package/dist/api/send.js.map +1 -1
- package/dist/api/slim.d.ts +14 -0
- package/dist/api/slim.d.ts.map +1 -0
- package/dist/api/slim.js +174 -0
- package/dist/api/slim.js.map +1 -0
- package/dist/cache-command.d.ts +12 -0
- package/dist/cache-command.d.ts.map +1 -0
- package/dist/cache-command.js +93 -0
- package/dist/cache-command.js.map +1 -0
- package/dist/check/command.d.ts +48 -0
- package/dist/check/command.d.ts.map +1 -0
- package/dist/check/command.js +256 -0
- package/dist/check/command.js.map +1 -0
- package/dist/check/external.d.ts +66 -0
- package/dist/check/external.d.ts.map +1 -0
- package/dist/check/external.js +177 -0
- package/dist/check/external.js.map +1 -0
- package/dist/check/markdown-links.d.ts +38 -0
- package/dist/check/markdown-links.d.ts.map +1 -0
- package/dist/check/markdown-links.js +166 -0
- package/dist/check/markdown-links.js.map +1 -0
- package/dist/cli.js +42 -7
- package/dist/cli.js.map +1 -1
- package/dist/commands/completion.d.ts.map +1 -1
- package/dist/commands/completion.js +17 -0
- package/dist/commands/completion.js.map +1 -1
- package/dist/commands/context.d.ts.map +1 -1
- package/dist/commands/context.js +6 -2
- package/dist/commands/context.js.map +1 -1
- package/dist/commands/groups-api.d.ts.map +1 -1
- package/dist/commands/groups-api.js +20 -2
- package/dist/commands/groups-api.js.map +1 -1
- package/dist/commands/groups-docs.d.ts.map +1 -1
- package/dist/commands/groups-docs.js +8 -2
- package/dist/commands/groups-docs.js.map +1 -1
- package/dist/commands/groups-jobs.d.ts +10 -0
- package/dist/commands/groups-jobs.d.ts.map +1 -0
- package/dist/commands/groups-jobs.js +89 -0
- package/dist/commands/groups-jobs.js.map +1 -0
- package/dist/commands/groups-org.d.ts.map +1 -1
- package/dist/commands/groups-org.js +59 -2
- package/dist/commands/groups-org.js.map +1 -1
- package/dist/commands/groups-site.d.ts.map +1 -1
- package/dist/commands/groups-site.js +47 -10
- package/dist/commands/groups-site.js.map +1 -1
- package/dist/commands/groups-spaces.d.ts.map +1 -1
- package/dist/commands/groups-spaces.js +11 -5
- package/dist/commands/groups-spaces.js.map +1 -1
- package/dist/commands/groups-types.d.ts.map +1 -1
- package/dist/commands/groups-types.js +74 -0
- package/dist/commands/groups-types.js.map +1 -1
- package/dist/commands/groups.d.ts.map +1 -1
- package/dist/commands/groups.js +2 -0
- package/dist/commands/groups.js.map +1 -1
- package/dist/commands/index.d.ts +9 -0
- package/dist/commands/index.d.ts.map +1 -1
- package/dist/commands/index.js +18 -3
- package/dist/commands/index.js.map +1 -1
- package/dist/commands/job-wait.d.ts +64 -0
- package/dist/commands/job-wait.d.ts.map +1 -0
- package/dist/commands/job-wait.js +91 -0
- package/dist/commands/job-wait.js.map +1 -0
- package/dist/commands/resource.d.ts +19 -0
- package/dist/commands/resource.d.ts.map +1 -1
- package/dist/commands/resource.js +65 -3
- package/dist/commands/resource.js.map +1 -1
- package/dist/content/library.d.ts +8 -0
- package/dist/content/library.d.ts.map +1 -1
- package/dist/content/library.js +10 -2
- package/dist/content/library.js.map +1 -1
- package/dist/credentials.d.ts +43 -4
- package/dist/credentials.d.ts.map +1 -1
- package/dist/credentials.js +67 -4
- package/dist/credentials.js.map +1 -1
- package/dist/dev/command.d.ts +41 -0
- package/dist/dev/command.d.ts.map +1 -0
- package/dist/dev/command.js +158 -0
- package/dist/dev/command.js.map +1 -0
- package/dist/doctor/address-checks.d.ts +56 -0
- package/dist/doctor/address-checks.d.ts.map +1 -0
- package/dist/doctor/address-checks.js +183 -0
- package/dist/doctor/address-checks.js.map +1 -0
- package/dist/doctor/boilerplate.d.ts +33 -0
- package/dist/doctor/boilerplate.d.ts.map +1 -0
- package/dist/doctor/boilerplate.js +78 -0
- package/dist/doctor/boilerplate.js.map +1 -0
- package/dist/doctor/command.d.ts +18 -0
- package/dist/doctor/command.d.ts.map +1 -0
- package/dist/doctor/command.js +234 -0
- package/dist/doctor/command.js.map +1 -0
- package/dist/doctor/crawl.d.ts +49 -0
- package/dist/doctor/crawl.d.ts.map +1 -0
- package/dist/doctor/crawl.js +210 -0
- package/dist/doctor/crawl.js.map +1 -0
- package/dist/doctor/extended.d.ts +52 -0
- package/dist/doctor/extended.d.ts.map +1 -0
- package/dist/doctor/extended.js +150 -0
- package/dist/doctor/extended.js.map +1 -0
- package/dist/doctor/page-checks.d.ts +42 -0
- package/dist/doctor/page-checks.d.ts.map +1 -0
- package/dist/doctor/page-checks.js +200 -0
- package/dist/doctor/page-checks.js.map +1 -0
- package/dist/doctor/probe.d.ts +63 -0
- package/dist/doctor/probe.d.ts.map +1 -0
- package/dist/doctor/probe.js +170 -0
- package/dist/doctor/probe.js.map +1 -0
- package/dist/doctor/reader-auth-atlas.d.ts +18 -0
- package/dist/doctor/reader-auth-atlas.d.ts.map +1 -0
- package/dist/doctor/reader-auth-atlas.js +50 -0
- package/dist/doctor/reader-auth-atlas.js.map +1 -0
- package/dist/doctor/robots-params.d.ts +36 -0
- package/dist/doctor/robots-params.d.ts.map +1 -0
- package/dist/doctor/robots-params.js +97 -0
- package/dist/doctor/robots-params.js.map +1 -0
- package/dist/doctor/site.d.ts +42 -0
- package/dist/doctor/site.d.ts.map +1 -0
- package/dist/doctor/site.js +82 -0
- package/dist/doctor/site.js.map +1 -0
- package/dist/doctor.d.ts +21 -0
- package/dist/doctor.d.ts.map +1 -1
- package/dist/doctor.js +94 -3
- package/dist/doctor.js.map +1 -1
- package/dist/exit-codes.d.ts +40 -0
- package/dist/exit-codes.d.ts.map +1 -0
- package/dist/exit-codes.js +66 -0
- package/dist/exit-codes.js.map +1 -0
- package/dist/help-commands.d.ts +13 -0
- package/dist/help-commands.d.ts.map +1 -0
- package/dist/help-commands.js +215 -0
- package/dist/help-commands.js.map +1 -0
- package/dist/help.d.ts +1 -1
- package/dist/help.d.ts.map +1 -1
- package/dist/help.js +74 -11
- package/dist/help.js.map +1 -1
- package/dist/io.d.ts +33 -0
- package/dist/io.d.ts.map +1 -0
- package/dist/io.js +81 -0
- package/dist/io.js.map +1 -0
- package/dist/preview-comment.d.ts +20 -0
- package/dist/preview-comment.d.ts.map +1 -0
- package/dist/preview-comment.js +88 -0
- package/dist/preview-comment.js.map +1 -0
- package/dist/publish/about.d.ts +23 -0
- package/dist/publish/about.d.ts.map +1 -0
- package/dist/publish/about.js +66 -0
- package/dist/publish/about.js.map +1 -0
- package/dist/publish/api.d.ts +30 -12
- package/dist/publish/api.d.ts.map +1 -1
- package/dist/publish/api.js +62 -10
- package/dist/publish/api.js.map +1 -1
- package/dist/publish/client.d.ts +19 -0
- package/dist/publish/client.d.ts.map +1 -0
- package/dist/publish/client.js +3 -0
- package/dist/publish/client.js.map +1 -0
- package/dist/publish/command.d.ts +51 -2
- package/dist/publish/command.d.ts.map +1 -1
- package/dist/publish/command.js +157 -17
- package/dist/publish/command.js.map +1 -1
- package/dist/publish/context.d.ts +12 -1
- package/dist/publish/context.d.ts.map +1 -1
- package/dist/publish/context.js.map +1 -1
- package/dist/publish/diff.d.ts +21 -0
- package/dist/publish/diff.d.ts.map +1 -0
- package/dist/publish/diff.js +96 -0
- package/dist/publish/diff.js.map +1 -0
- package/dist/publish/docs-apply.d.ts +8 -1
- package/dist/publish/docs-apply.d.ts.map +1 -1
- package/dist/publish/docs-apply.js +49 -10
- package/dist/publish/docs-apply.js.map +1 -1
- package/dist/publish/docs-plan.d.ts +16 -3
- package/dist/publish/docs-plan.d.ts.map +1 -1
- package/dist/publish/docs-plan.js +57 -6
- package/dist/publish/docs-plan.js.map +1 -1
- package/dist/publish/extras.d.ts +10 -0
- package/dist/publish/extras.d.ts.map +1 -1
- package/dist/publish/extras.js +54 -2
- package/dist/publish/extras.js.map +1 -1
- package/dist/publish/frontmatter.d.ts +1 -1
- package/dist/publish/frontmatter.d.ts.map +1 -1
- package/dist/publish/frontmatter.js +1 -0
- package/dist/publish/frontmatter.js.map +1 -1
- package/dist/publish/index.d.ts +12 -0
- package/dist/publish/index.d.ts.map +1 -0
- package/dist/publish/index.js +17 -0
- package/dist/publish/index.js.map +1 -0
- package/dist/publish/manifest.d.ts.map +1 -1
- package/dist/publish/manifest.js +48 -3
- package/dist/publish/manifest.js.map +1 -1
- package/dist/publish/names.d.ts +28 -0
- package/dist/publish/names.d.ts.map +1 -0
- package/dist/publish/names.js +80 -0
- package/dist/publish/names.js.map +1 -0
- package/dist/publish/posts.d.ts.map +1 -1
- package/dist/publish/posts.js +46 -13
- package/dist/publish/posts.js.map +1 -1
- package/dist/publish/preview.d.ts +56 -0
- package/dist/publish/preview.d.ts.map +1 -0
- package/dist/publish/preview.js +133 -0
- package/dist/publish/preview.js.map +1 -0
- package/dist/publish/redirects.d.ts +13 -0
- package/dist/publish/redirects.d.ts.map +1 -0
- package/dist/publish/redirects.js +154 -0
- package/dist/publish/redirects.js.map +1 -0
- package/dist/publish/reserved.d.ts +49 -0
- package/dist/publish/reserved.d.ts.map +1 -0
- package/dist/publish/reserved.js +118 -0
- package/dist/publish/reserved.js.map +1 -0
- package/dist/publish/rewrite.d.ts +12 -0
- package/dist/publish/rewrite.d.ts.map +1 -1
- package/dist/publish/rewrite.js +22 -0
- package/dist/publish/rewrite.js.map +1 -1
- package/dist/publish/scan-docs.d.ts +10 -0
- package/dist/publish/scan-docs.d.ts.map +1 -0
- package/dist/publish/scan-docs.js +160 -0
- package/dist/publish/scan-docs.js.map +1 -0
- package/dist/publish/scan.d.ts +21 -17
- package/dist/publish/scan.d.ts.map +1 -1
- package/dist/publish/scan.js +99 -232
- package/dist/publish/scan.js.map +1 -1
- package/dist/publish/slots.d.ts +39 -0
- package/dist/publish/slots.d.ts.map +1 -0
- package/dist/publish/slots.js +64 -0
- package/dist/publish/slots.js.map +1 -0
- package/dist/publish/studio-edits.d.ts +25 -0
- package/dist/publish/studio-edits.d.ts.map +1 -0
- package/dist/publish/studio-edits.js +53 -0
- package/dist/publish/studio-edits.js.map +1 -0
- package/dist/publish/tags.d.ts +9 -0
- package/dist/publish/tags.d.ts.map +1 -0
- package/dist/publish/tags.js +194 -0
- package/dist/publish/tags.js.map +1 -0
- package/dist/publish/types.d.ts +84 -1
- package/dist/publish/types.d.ts.map +1 -1
- package/dist/publish/types.js +0 -10
- package/dist/publish/types.js.map +1 -1
- package/dist/publish-command.d.ts +55 -0
- package/dist/publish-command.d.ts.map +1 -0
- package/dist/publish-command.js +197 -0
- package/dist/publish-command.js.map +1 -0
- package/dist/run.d.ts +3 -10
- package/dist/run.d.ts.map +1 -1
- package/dist/run.js +171 -112
- package/dist/run.js.map +1 -1
- package/dist/translations/target.d.ts.map +1 -1
- package/dist/translations/target.js +2 -2
- package/dist/translations/target.js.map +1 -1
- package/dist/validate.d.ts +39 -0
- package/dist/validate.d.ts.map +1 -0
- package/dist/validate.js +166 -0
- package/dist/validate.js.map +1 -0
- package/dist/version.d.ts +8 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +11 -0
- package/dist/version.js.map +1 -0
- package/dist/whoami.d.ts +41 -0
- package/dist/whoami.d.ts.map +1 -0
- package/dist/whoami.js +161 -0
- package/dist/whoami.js.map +1 -0
- package/package.json +2 -2
- package/dist/publish-dir.d.ts +0 -54
- package/dist/publish-dir.d.ts.map +0 -1
- package/dist/publish-dir.js +0 -119
- package/dist/publish-dir.js.map +0 -1
package/dist/credentials.d.ts
CHANGED
|
@@ -10,8 +10,10 @@
|
|
|
10
10
|
* Keyed by API base URL, so a token for staging cannot be sent to production by
|
|
11
11
|
* forgetting to change one variable.
|
|
12
12
|
*
|
|
13
|
-
* `FORUMULATE_TOKEN` still wins when it is set. CI sets it, and a
|
|
14
|
-
* silently overriding what a pipeline passed in would be the worse
|
|
13
|
+
* `FORUMULATE_TOKEN` still wins over the file when it is set. CI sets it, and a
|
|
14
|
+
* file on disk silently overriding what a pipeline passed in would be the worse
|
|
15
|
+
* surprise. A `--token` on the command line wins over both: it is the most
|
|
16
|
+
* specific thing the caller said.
|
|
15
17
|
*/
|
|
16
18
|
export interface Credentials {
|
|
17
19
|
/** baseUrl → token. */
|
|
@@ -19,8 +21,45 @@ export interface Credentials {
|
|
|
19
21
|
}
|
|
20
22
|
export declare function credentialsPath(env?: NodeJS.ProcessEnv): string;
|
|
21
23
|
export declare function readCredentials(env?: NodeJS.ProcessEnv): Credentials;
|
|
22
|
-
/**
|
|
23
|
-
export
|
|
24
|
+
/** Where a token came from (FMU-D06): the answer `whoami` prints first. */
|
|
25
|
+
export type TokenSource = 'flag' | 'env' | 'credentials' | 'none';
|
|
26
|
+
/**
|
|
27
|
+
* The token for a base URL, and where it was found: `--token` beats
|
|
28
|
+
* `FORUMULATE_TOKEN` beats `credentials.json`. An integrator with one PAT in
|
|
29
|
+
* the shell, another in the file and a third in CI could not tell which one a
|
|
30
|
+
* 401 was about; this is the precedence, said once.
|
|
31
|
+
*/
|
|
32
|
+
export declare function resolveTokenSource(baseUrl: string, env?: NodeJS.ProcessEnv, flagToken?: string): {
|
|
33
|
+
token?: string;
|
|
34
|
+
source: TokenSource;
|
|
35
|
+
path?: string;
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* The token for a base URL, by the same precedence as `resolveTokenSource`:
|
|
39
|
+
* `--token`, then the environment, then what `login` stored.
|
|
40
|
+
*
|
|
41
|
+
* This used to skip the flag. `cli.ts` read `--token` for the SDK client only,
|
|
42
|
+
* so `whoami --token x` used x while `items list --token x`, `api …`, `doctor
|
|
43
|
+
* --site` and `validate` (each building its own transport through here) sent
|
|
44
|
+
* `FORUMULATE_TOKEN` or the stored token instead: the documented precedence
|
|
45
|
+
* held for two commands (FMU-DOC01). One resolver, every caller.
|
|
46
|
+
*/
|
|
47
|
+
export declare function resolveToken(baseUrl: string, env?: NodeJS.ProcessEnv, flagToken?: string): string | undefined;
|
|
48
|
+
/**
|
|
49
|
+
* Take the global `--token <pat>` / `--token=<pat>` out of an argument list.
|
|
50
|
+
*
|
|
51
|
+
* The older commands in `run.ts` parse against a fixed list of value flags,
|
|
52
|
+
* so the flag and its value are removed before they see the arguments. The
|
|
53
|
+
* `api` and resource commands get the arguments as given: there an operation
|
|
54
|
+
* that declares a `token` parameter (a registry's, a GitHub connection's)
|
|
55
|
+
* owns the flag, and `api/request.ts` keeps it out of the query string
|
|
56
|
+
* everywhere else. Nothing after `--` is touched; the last `--token` wins, as
|
|
57
|
+
* with every other repeated flag.
|
|
58
|
+
*/
|
|
59
|
+
export declare function takeTokenFlag(argv: string[]): {
|
|
60
|
+
argv: string[];
|
|
61
|
+
token?: string;
|
|
62
|
+
};
|
|
24
63
|
export declare function storeToken(baseUrl: string, token: string, env?: NodeJS.ProcessEnv): string;
|
|
25
64
|
export declare function forgetToken(baseUrl: string, env?: NodeJS.ProcessEnv): boolean;
|
|
26
65
|
/** One spelling per host: a trailing slash is not a different server. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"credentials.d.ts","sourceRoot":"","sources":["../src/credentials.ts"],"names":[],"mappings":"AAIA
|
|
1
|
+
{"version":3,"file":"credentials.d.ts","sourceRoot":"","sources":["../src/credentials.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,WAAW,WAAW;IAC1B,uBAAuB;IACvB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAChC;AAED,wBAAgB,eAAe,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,CAO5E;AAED,wBAAgB,eAAe,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,WAAW,CASjF;AAED,2EAA2E;AAC3E,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,KAAK,GAAG,aAAa,GAAG,MAAM,CAAC;AAElE;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,MAAM,EACf,GAAG,GAAE,MAAM,CAAC,UAAwB,EACpC,SAAS,CAAC,EAAE,MAAM,GACjB;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,WAAW,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAMxD;AAED;;;;;;;;;GASG;AACH,wBAAgB,YAAY,CAC1B,OAAO,EAAE,MAAM,EACf,GAAG,GAAE,MAAM,CAAC,UAAwB,EACpC,SAAS,CAAC,EAAE,MAAM,GACjB,MAAM,GAAG,SAAS,CAEpB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG;IAAE,IAAI,EAAE,MAAM,EAAE,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAqBhF;AAED,wBAAgB,UAAU,CACxB,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,EACb,GAAG,GAAE,MAAM,CAAC,UAAwB,GACnC,MAAM,CAUR;AAED,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,OAAO,CAiB1F;AAED,yEAAyE;AACzE,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAErD"}
|
package/dist/credentials.js
CHANGED
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.credentialsPath = credentialsPath;
|
|
4
4
|
exports.readCredentials = readCredentials;
|
|
5
|
+
exports.resolveTokenSource = resolveTokenSource;
|
|
5
6
|
exports.resolveToken = resolveToken;
|
|
7
|
+
exports.takeTokenFlag = takeTokenFlag;
|
|
6
8
|
exports.storeToken = storeToken;
|
|
7
9
|
exports.forgetToken = forgetToken;
|
|
8
10
|
exports.normalizeBase = normalizeBase;
|
|
@@ -27,11 +29,72 @@ function readCredentials(env = process.env) {
|
|
|
27
29
|
return { tokens: {} };
|
|
28
30
|
}
|
|
29
31
|
}
|
|
30
|
-
/**
|
|
31
|
-
|
|
32
|
+
/**
|
|
33
|
+
* The token for a base URL, and where it was found: `--token` beats
|
|
34
|
+
* `FORUMULATE_TOKEN` beats `credentials.json`. An integrator with one PAT in
|
|
35
|
+
* the shell, another in the file and a third in CI could not tell which one a
|
|
36
|
+
* 401 was about; this is the precedence, said once.
|
|
37
|
+
*/
|
|
38
|
+
function resolveTokenSource(baseUrl, env = process.env, flagToken) {
|
|
39
|
+
if (flagToken?.trim())
|
|
40
|
+
return { token: flagToken.trim(), source: 'flag' };
|
|
32
41
|
if (env.FORUMULATE_TOKEN)
|
|
33
|
-
return env.FORUMULATE_TOKEN;
|
|
34
|
-
|
|
42
|
+
return { token: env.FORUMULATE_TOKEN, source: 'env' };
|
|
43
|
+
const stored = readCredentials(env).tokens[normalizeBase(baseUrl)];
|
|
44
|
+
if (stored)
|
|
45
|
+
return { token: stored, source: 'credentials', path: credentialsPath(env) };
|
|
46
|
+
return { source: 'none' };
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* The token for a base URL, by the same precedence as `resolveTokenSource`:
|
|
50
|
+
* `--token`, then the environment, then what `login` stored.
|
|
51
|
+
*
|
|
52
|
+
* This used to skip the flag. `cli.ts` read `--token` for the SDK client only,
|
|
53
|
+
* so `whoami --token x` used x while `items list --token x`, `api …`, `doctor
|
|
54
|
+
* --site` and `validate` (each building its own transport through here) sent
|
|
55
|
+
* `FORUMULATE_TOKEN` or the stored token instead: the documented precedence
|
|
56
|
+
* held for two commands (FMU-DOC01). One resolver, every caller.
|
|
57
|
+
*/
|
|
58
|
+
function resolveToken(baseUrl, env = process.env, flagToken) {
|
|
59
|
+
return resolveTokenSource(baseUrl, env, flagToken).token;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Take the global `--token <pat>` / `--token=<pat>` out of an argument list.
|
|
63
|
+
*
|
|
64
|
+
* The older commands in `run.ts` parse against a fixed list of value flags,
|
|
65
|
+
* so the flag and its value are removed before they see the arguments. The
|
|
66
|
+
* `api` and resource commands get the arguments as given: there an operation
|
|
67
|
+
* that declares a `token` parameter (a registry's, a GitHub connection's)
|
|
68
|
+
* owns the flag, and `api/request.ts` keeps it out of the query string
|
|
69
|
+
* everywhere else. Nothing after `--` is touched; the last `--token` wins, as
|
|
70
|
+
* with every other repeated flag.
|
|
71
|
+
*/
|
|
72
|
+
function takeTokenFlag(argv) {
|
|
73
|
+
const rest = [];
|
|
74
|
+
let token;
|
|
75
|
+
for (let i = 0; i < argv.length; i++) {
|
|
76
|
+
const a = argv[i];
|
|
77
|
+
if (a === '--') {
|
|
78
|
+
rest.push(...argv.slice(i));
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
if (a.startsWith('--token='))
|
|
82
|
+
token = a.slice('--token='.length);
|
|
83
|
+
else if (a === '--token') {
|
|
84
|
+
const next = argv[i + 1];
|
|
85
|
+
// `--token --json` is a missing value, not a token called "--json".
|
|
86
|
+
if (next !== undefined && !next.startsWith('-')) {
|
|
87
|
+
token = next;
|
|
88
|
+
i += 1;
|
|
89
|
+
}
|
|
90
|
+
else
|
|
91
|
+
token = '';
|
|
92
|
+
}
|
|
93
|
+
else
|
|
94
|
+
rest.push(a);
|
|
95
|
+
}
|
|
96
|
+
const trimmed = token?.trim();
|
|
97
|
+
return { argv: rest, token: trimmed ? trimmed : undefined };
|
|
35
98
|
}
|
|
36
99
|
function storeToken(baseUrl, token, env = process.env) {
|
|
37
100
|
const path = credentialsPath(env);
|
package/dist/credentials.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"credentials.js","sourceRoot":"","sources":["../src/credentials.ts"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"credentials.js","sourceRoot":"","sources":["../src/credentials.ts"],"names":[],"mappings":";;AA0BA,0CAOC;AAED,0CASC;AAWD,gDAUC;AAYD,oCAMC;AAaD,sCAqBC;AAED,gCAcC;AAED,kCAiBC;AAGD,sCAEC;AA7JD,qCAAoF;AACpF,qCAAkC;AAClC,yCAA0C;AAwB1C,SAAgB,eAAe,CAAC,MAAyB,OAAO,CAAC,GAAG;IAClE,MAAM,IAAI,GACR,GAAG,CAAC,qBAAqB;QACzB,CAAC,GAAG,CAAC,eAAe;YAClB,CAAC,CAAC,IAAA,gBAAI,EAAC,GAAG,CAAC,eAAe,EAAE,YAAY,CAAC;YACzC,CAAC,CAAC,IAAA,gBAAI,EAAC,IAAA,iBAAO,GAAE,EAAE,SAAS,EAAE,YAAY,CAAC,CAAC,CAAC;IAChD,OAAO,IAAA,gBAAI,EAAC,IAAI,EAAE,kBAAkB,CAAC,CAAC;AACxC,CAAC;AAED,SAAgB,eAAe,CAAC,MAAyB,OAAO,CAAC,GAAG;IAClE,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAA,sBAAY,EAAC,eAAe,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,CAAyB,CAAC;QAC9F,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,IAAI,EAAE,EAAE,CAAC;IACzC,CAAC;IAAC,MAAM,CAAC;QACP,6EAA6E;QAC7E,+BAA+B;QAC/B,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;IACxB,CAAC;AACH,CAAC;AAKD;;;;;GAKG;AACH,SAAgB,kBAAkB,CAChC,OAAe,EACf,MAAyB,OAAO,CAAC,GAAG,EACpC,SAAkB;IAElB,IAAI,SAAS,EAAE,IAAI,EAAE;QAAE,OAAO,EAAE,KAAK,EAAE,SAAS,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;IAC1E,IAAI,GAAG,CAAC,gBAAgB;QAAE,OAAO,EAAE,KAAK,EAAE,GAAG,CAAC,gBAAgB,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;IAChF,MAAM,MAAM,GAAG,eAAe,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC,CAAC;IACnE,IAAI,MAAM;QAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,EAAE,IAAI,EAAE,eAAe,CAAC,GAAG,CAAC,EAAE,CAAC;IACxF,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;AAC5B,CAAC;AAED;;;;;;;;;GASG;AACH,SAAgB,YAAY,CAC1B,OAAe,EACf,MAAyB,OAAO,CAAC,GAAG,EACpC,SAAkB;IAElB,OAAO,kBAAkB,CAAC,OAAO,EAAE,GAAG,EAAE,SAAS,CAAC,CAAC,KAAK,CAAC;AAC3D,CAAC;AAED;;;;;;;;;;GAUG;AACH,SAAgB,aAAa,CAAC,IAAc;IAC1C,MAAM,IAAI,GAAa,EAAE,CAAC;IAC1B,IAAI,KAAyB,CAAC;IAC9B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACrC,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC,CAAE,CAAC;QACnB,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC;YACf,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;YAC5B,MAAM;QACR,CAAC;QACD,IAAI,CAAC,CAAC,UAAU,CAAC,UAAU,CAAC;YAAE,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;aAC5D,IAAI,CAAC,KAAK,SAAS,EAAE,CAAC;YACzB,MAAM,IAAI,GAAG,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;YACzB,oEAAoE;YACpE,IAAI,IAAI,KAAK,SAAS,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;gBAChD,KAAK,GAAG,IAAI,CAAC;gBACb,CAAC,IAAI,CAAC,CAAC;YACT,CAAC;;gBAAM,KAAK,GAAG,EAAE,CAAC;QACpB,CAAC;;YAAM,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACtB,CAAC;IACD,MAAM,OAAO,GAAG,KAAK,EAAE,IAAI,EAAE,CAAC;IAC9B,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC;AAC9D,CAAC;AAED,SAAgB,UAAU,CACxB,OAAe,EACf,KAAa,EACb,MAAyB,OAAO,CAAC,GAAG;IAEpC,MAAM,IAAI,GAAG,eAAe,CAAC,GAAG,CAAC,CAAC;IAClC,MAAM,KAAK,GAAG,eAAe,CAAC,GAAG,CAAC,CAAC;IACnC,KAAK,CAAC,MAAM,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC,GAAG,KAAK,CAAC;IAC7C,IAAA,mBAAS,EAAC,IAAA,mBAAO,EAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IAC3D,IAAA,uBAAa,EAAC,IAAI,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IAC5E,8EAA8E;IAC9E,0EAA0E;IAC1E,IAAA,mBAAS,EAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IACvB,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAgB,WAAW,CAAC,OAAe,EAAE,MAAyB,OAAO,CAAC,GAAG;IAC/E,MAAM,KAAK,GAAG,eAAe,CAAC,GAAG,CAAC,CAAC;IACnC,MAAM,GAAG,GAAG,aAAa,CAAC,OAAO,CAAC,CAAC;IACnC,IAAI,CAAC,CAAC,GAAG,IAAI,KAAK,CAAC,MAAM,CAAC;QAAE,OAAO,KAAK,CAAC;IACzC,OAAO,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IACzB,MAAM,IAAI,GAAG,eAAe,CAAC,GAAG,CAAC,CAAC;IAClC,IAAI,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC3C,IAAI,CAAC;YACH,IAAA,gBAAM,EAAC,IAAI,CAAC,CAAC;QACf,CAAC;QAAC,MAAM,CAAC;YACP,kBAAkB;QACpB,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IACD,IAAA,uBAAa,EAAC,IAAI,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IAC5E,IAAA,mBAAS,EAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IACvB,OAAO,IAAI,CAAC;AACd,CAAC;AAED,yEAAyE;AACzE,SAAgB,aAAa,CAAC,OAAe;IAC3C,OAAO,OAAO,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;AACnD,CAAC"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { ForumulateClient } from '@forumulate/sdk';
|
|
2
|
+
/**
|
|
3
|
+
* `forumulate dev <folder> --space <spaceId> [--org <orgId>] [--port 4321] [--site <url>]`
|
|
4
|
+
* — a local preview of a docs folder, reloading as you save (Mintlify's
|
|
5
|
+
* `mint dev`, Docusaurus's `start`; DOCS #244).
|
|
6
|
+
*
|
|
7
|
+
* Every page is rendered by the API's own renderer (`POST …/docs/render`),
|
|
8
|
+
* not by a second one shipped in the CLI, so what you see is what readers
|
|
9
|
+
* will get. `--site` (your published docs address) lends the preview its
|
|
10
|
+
* look: the stylesheets that page links and the theme it inlines are copied
|
|
11
|
+
* once into each preview page. Nothing is written anywhere.
|
|
12
|
+
*/
|
|
13
|
+
export interface DevOptions {
|
|
14
|
+
folder: string;
|
|
15
|
+
org?: string;
|
|
16
|
+
space?: string;
|
|
17
|
+
port: number;
|
|
18
|
+
site?: string;
|
|
19
|
+
}
|
|
20
|
+
export interface DevDeps {
|
|
21
|
+
client: ForumulateClient;
|
|
22
|
+
out: (line: string) => void;
|
|
23
|
+
err: (line: string) => void;
|
|
24
|
+
/** Injected so tests can stop it; production keeps the process alive. */
|
|
25
|
+
onListening?: (close: () => void) => void;
|
|
26
|
+
}
|
|
27
|
+
/** Every Markdown file under the folder, as `/`-separated relative paths, sorted. */
|
|
28
|
+
export declare function markdownFiles(folder: string, dir?: string): string[];
|
|
29
|
+
/** The stylesheet links and inline theme of a published page, to dress the preview in. */
|
|
30
|
+
export declare function themeOf(html: string, site: string): string;
|
|
31
|
+
/** One preview document: the nav of files, the rendered body, and the reload hook. */
|
|
32
|
+
export declare function previewPage(opts: {
|
|
33
|
+
title: string;
|
|
34
|
+
heading?: string | null;
|
|
35
|
+
body: string;
|
|
36
|
+
files: string[];
|
|
37
|
+
current: string | null;
|
|
38
|
+
theme: string;
|
|
39
|
+
}): string;
|
|
40
|
+
export declare function runDev(opts: DevOptions, deps: DevDeps): Promise<number>;
|
|
41
|
+
//# sourceMappingURL=command.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"command.d.ts","sourceRoot":"","sources":["../../src/dev/command.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAGxD;;;;;;;;;;GAUG;AACH,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,OAAO;IACtB,MAAM,EAAE,gBAAgB,CAAC;IACzB,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5B,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5B,yEAAyE;IACzE,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,IAAI,KAAK,IAAI,CAAC;CAC3C;AAID,qFAAqF;AACrF,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,SAAK,GAAG,MAAM,EAAE,CAShE;AAED,0FAA0F;AAC1F,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAM1D;AAKD,sFAAsF;AACtF,wBAAgB,WAAW,CAAC,IAAI,EAAE;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,KAAK,EAAE,MAAM,CAAC;CACf,GAAG,MAAM,CAWT;AAED,wBAAsB,MAAM,CAAC,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAwH7E"}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.markdownFiles = markdownFiles;
|
|
7
|
+
exports.themeOf = themeOf;
|
|
8
|
+
exports.previewPage = previewPage;
|
|
9
|
+
exports.runDev = runDev;
|
|
10
|
+
const node_http_1 = require("node:http");
|
|
11
|
+
const node_fs_1 = require("node:fs");
|
|
12
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
13
|
+
const frontmatter_1 = require("../publish/frontmatter");
|
|
14
|
+
const PAGE = /\.(md|mdx|markdown)$/i;
|
|
15
|
+
/** Every Markdown file under the folder, as `/`-separated relative paths, sorted. */
|
|
16
|
+
function markdownFiles(folder, dir = '') {
|
|
17
|
+
const out = [];
|
|
18
|
+
for (const name of (0, node_fs_1.readdirSync)(node_path_1.default.join(folder, dir))) {
|
|
19
|
+
if (name.startsWith('.') || name === 'node_modules')
|
|
20
|
+
continue;
|
|
21
|
+
const rel = dir ? `${dir}/${name}` : name;
|
|
22
|
+
if ((0, node_fs_1.statSync)(node_path_1.default.join(folder, rel)).isDirectory())
|
|
23
|
+
out.push(...markdownFiles(folder, rel));
|
|
24
|
+
else if (PAGE.test(name))
|
|
25
|
+
out.push(rel);
|
|
26
|
+
}
|
|
27
|
+
return out.sort();
|
|
28
|
+
}
|
|
29
|
+
/** The stylesheet links and inline theme of a published page, to dress the preview in. */
|
|
30
|
+
function themeOf(html, site) {
|
|
31
|
+
const links = [...html.matchAll(/<link[^>]+rel="stylesheet"[^>]*>/gi)].map((m) => m[0].replace(/href="(\/[^"]*)"/, (_x, href) => `href="${new URL(href, site).href}"`));
|
|
32
|
+
const styles = [...html.matchAll(/<style[^>]*>[\s\S]*?<\/style>/gi)].map((m) => m[0]);
|
|
33
|
+
return [...links, ...styles].join('\n');
|
|
34
|
+
}
|
|
35
|
+
const esc = (s) => s.replace(/[&<>"]/g, (c) => ({ '&': '&', '<': '<', '>': '>', '"': '"' })[c]);
|
|
36
|
+
/** One preview document: the nav of files, the rendered body, and the reload hook. */
|
|
37
|
+
function previewPage(opts) {
|
|
38
|
+
const nav = opts.files
|
|
39
|
+
.map((f) => `<li><a href="/${esc(f)}"${f === opts.current ? ' aria-current="page" style="font-weight:600"' : ''}>${esc(f)}</a></li>`)
|
|
40
|
+
.join('');
|
|
41
|
+
return `<!doctype html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>${esc(opts.title)} · preview</title>${opts.theme}
|
|
42
|
+
<style>.fm-dev{display:flex;gap:2rem;max-width:72rem;margin:0 auto;padding:2rem 1rem}.fm-dev nav{flex:0 0 14rem;font:14px/1.6 system-ui,sans-serif}.fm-dev nav ul{list-style:none;padding:0;margin:0}.fm-dev main{flex:1;min-width:0}</style></head>
|
|
43
|
+
<body class="forumulate-reader"><div class="fm-dev"><nav aria-label="Files"><ul>${nav}</ul></nav><main><article class="forumulate-prose">${opts.heading ? `<h1>${esc(opts.heading)}</h1>` : ''}${opts.body}</article></main></div>
|
|
44
|
+
<script>new EventSource('/__forumulate/live').onmessage=function(){location.reload()};</script></body></html>`;
|
|
45
|
+
}
|
|
46
|
+
async function runDev(opts, deps) {
|
|
47
|
+
if (!opts.space || !opts.org) {
|
|
48
|
+
deps.err('dev needs --org <orgId> and --space <spaceId> (the space whose renderer previews it).');
|
|
49
|
+
return 2;
|
|
50
|
+
}
|
|
51
|
+
const folder = node_path_1.default.resolve(opts.folder);
|
|
52
|
+
let theme = '';
|
|
53
|
+
if (opts.site) {
|
|
54
|
+
try {
|
|
55
|
+
theme = themeOf(await (await fetch(opts.site)).text(), opts.site);
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
deps.err(`Could not read ${opts.site}; the preview will be unstyled.`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
const clients = new Set();
|
|
62
|
+
const render = async (rel) => {
|
|
63
|
+
const { data, body } = (0, frontmatter_1.splitFront)((0, node_fs_1.readFileSync)(node_path_1.default.join(folder, rel), 'utf8'));
|
|
64
|
+
const r = await deps.client.request('POST', `/orgs/${opts.org}/spaces/${opts.space}/docs/render`, { body: { markdown: body } });
|
|
65
|
+
const declared = typeof data.title === 'string' ? data.title : null;
|
|
66
|
+
// A body that opens with its own `# Title` already carries it; a front-matter
|
|
67
|
+
// title is drawn as the heading only when the body has none.
|
|
68
|
+
return {
|
|
69
|
+
title: declared ?? r.title ?? rel,
|
|
70
|
+
heading: declared && !r.title ? declared : null,
|
|
71
|
+
html: r.html,
|
|
72
|
+
};
|
|
73
|
+
};
|
|
74
|
+
const server = (0, node_http_1.createServer)((req, res) => {
|
|
75
|
+
void (async () => {
|
|
76
|
+
const url = decodeURIComponent((req.url ?? '/').split('?')[0]);
|
|
77
|
+
if (url === '/__forumulate/live') {
|
|
78
|
+
res.writeHead(200, {
|
|
79
|
+
'content-type': 'text/event-stream',
|
|
80
|
+
'cache-control': 'no-store',
|
|
81
|
+
connection: 'keep-alive',
|
|
82
|
+
});
|
|
83
|
+
res.write(': live\n\n');
|
|
84
|
+
clients.add(res);
|
|
85
|
+
req.on('close', () => clients.delete(res));
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
const files = markdownFiles(folder);
|
|
89
|
+
const rel = url === '/'
|
|
90
|
+
? (files.find((f) => /(^|\/)(index|readme)\.mdx?$/i.test(f)) ?? files[0] ?? null)
|
|
91
|
+
: url.slice(1);
|
|
92
|
+
// Only a file the listing holds — never a path that climbs out of the folder.
|
|
93
|
+
if (!rel || !files.includes(rel)) {
|
|
94
|
+
res.writeHead(404, { 'content-type': 'text/html; charset=utf-8' });
|
|
95
|
+
res.end(previewPage({
|
|
96
|
+
title: 'Not found',
|
|
97
|
+
body: '<p>No such page in this folder.</p>',
|
|
98
|
+
files,
|
|
99
|
+
current: null,
|
|
100
|
+
theme,
|
|
101
|
+
}));
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
try {
|
|
105
|
+
const page = await render(rel);
|
|
106
|
+
res.writeHead(200, {
|
|
107
|
+
'content-type': 'text/html; charset=utf-8',
|
|
108
|
+
'cache-control': 'no-store',
|
|
109
|
+
});
|
|
110
|
+
res.end(previewPage({
|
|
111
|
+
title: page.title,
|
|
112
|
+
heading: page.heading,
|
|
113
|
+
body: page.html,
|
|
114
|
+
files,
|
|
115
|
+
current: rel,
|
|
116
|
+
theme,
|
|
117
|
+
}));
|
|
118
|
+
}
|
|
119
|
+
catch (e) {
|
|
120
|
+
res.writeHead(502, { 'content-type': 'text/html; charset=utf-8' });
|
|
121
|
+
res.end(previewPage({
|
|
122
|
+
title: 'Could not render',
|
|
123
|
+
body: `<pre>${esc(String(e.message ?? e))}</pre>`,
|
|
124
|
+
files,
|
|
125
|
+
current: rel,
|
|
126
|
+
theme,
|
|
127
|
+
}));
|
|
128
|
+
}
|
|
129
|
+
})();
|
|
130
|
+
});
|
|
131
|
+
// Saved a file: every open preview reloads (a burst of writes is one reload).
|
|
132
|
+
let timer = null;
|
|
133
|
+
const watcher = (0, node_fs_1.watch)(folder, { recursive: true }, (_e, name) => {
|
|
134
|
+
if (!name || !PAGE.test(String(name)))
|
|
135
|
+
return;
|
|
136
|
+
if (timer)
|
|
137
|
+
clearTimeout(timer);
|
|
138
|
+
timer = setTimeout(() => {
|
|
139
|
+
for (const c of clients)
|
|
140
|
+
c.write('data: reload\n\n');
|
|
141
|
+
}, 120);
|
|
142
|
+
});
|
|
143
|
+
await new Promise((resolve) => server.listen(opts.port, '127.0.0.1', resolve));
|
|
144
|
+
deps.out(`Previewing ${folder} at http://127.0.0.1:${opts.port}/ — Ctrl+C to stop.`);
|
|
145
|
+
const close = () => {
|
|
146
|
+
watcher.close();
|
|
147
|
+
for (const c of clients)
|
|
148
|
+
c.end();
|
|
149
|
+
server.close();
|
|
150
|
+
};
|
|
151
|
+
if (deps.onListening) {
|
|
152
|
+
deps.onListening(close);
|
|
153
|
+
return 0;
|
|
154
|
+
}
|
|
155
|
+
await new Promise((resolve) => server.on('close', resolve));
|
|
156
|
+
return 0;
|
|
157
|
+
}
|
|
158
|
+
//# sourceMappingURL=command.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"command.js","sourceRoot":"","sources":["../../src/dev/command.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { Check } from '../conformance';
|
|
2
|
+
import { type Get } from './probe';
|
|
3
|
+
/**
|
|
4
|
+
* Checks about a page that lives at more than one address.
|
|
5
|
+
*
|
|
6
|
+
* A hosted docs platform serves every published page twice: on its own host
|
|
7
|
+
* (`platform.example/p/<org>/<space>/<page>`) and on the customer's domain.
|
|
8
|
+
* Search engines must be told, by BOTH copies, that the customer's address is
|
|
9
|
+
* the real one. The platform copy of a LEGAL page redirected to a third
|
|
10
|
+
* address that named itself canonical (FMU-B01), so the customer's legal pages
|
|
11
|
+
* competed with a copy on our host. Nothing on the customer's domain showed it.
|
|
12
|
+
*/
|
|
13
|
+
/** One page at its several addresses: the primary (canonical) one first when known. */
|
|
14
|
+
export interface AddressSet {
|
|
15
|
+
/** What the canonical of every copy should be, when it is known. */
|
|
16
|
+
expected?: string;
|
|
17
|
+
urls: string[];
|
|
18
|
+
}
|
|
19
|
+
export declare function canonicalBothAddresses(get: Get, sets: AddressSet[]): Promise<Check>;
|
|
20
|
+
/**
|
|
21
|
+
* Strip what a browser does not show and a reader cannot click: scripts,
|
|
22
|
+
* styles, and `<link>` tags (preloads, stylesheets). A white-label site may
|
|
23
|
+
* well load its JavaScript from the platform's CDN — that is infrastructure,
|
|
24
|
+
* not branding — so only the markup a person sees, and the links they follow,
|
|
25
|
+
* are judged.
|
|
26
|
+
*/
|
|
27
|
+
export declare function visibleMarkup(html: string): string;
|
|
28
|
+
/** Platform URLs with a path: a bare-origin link is a "powered by" badge, which is a setting, not a leak. */
|
|
29
|
+
export declare function platformMentions(text: string, platformHost: string): string[];
|
|
30
|
+
/**
|
|
31
|
+
* `white-label-leaks`: a page on the customer's domain does not send its
|
|
32
|
+
* readers, or its agents, back to the platform's host.
|
|
33
|
+
*
|
|
34
|
+
* The Markdown twin of a reference page on a white-label domain ended with a
|
|
35
|
+
* "For AI agents" block pointing at the platform's API host for llms-full.txt
|
|
36
|
+
* and MCP, while the same files existed on the customer's domain (FMU-B15).
|
|
37
|
+
* The HTML looked clean; the agent-facing copy was the leak. Both are read.
|
|
38
|
+
*/
|
|
39
|
+
export declare function whiteLabelLeaks(get: Get, pageUrl: string, html: string, platformHost: string): Promise<Check>;
|
|
40
|
+
export declare function findCmps(html: string): {
|
|
41
|
+
name: string;
|
|
42
|
+
at: number[];
|
|
43
|
+
}[];
|
|
44
|
+
export declare function languageHints(html: string, at: number[]): string[];
|
|
45
|
+
/**
|
|
46
|
+
* `consent-tag`: the cookie banner speaks the page's language.
|
|
47
|
+
*
|
|
48
|
+
* The CMP tag carried no culture, so the banner followed the BROWSER's
|
|
49
|
+
* language rather than the page's (FMU-B17, S05): a German reader on the
|
|
50
|
+
* English page got a German banner over English text, and on a translated
|
|
51
|
+
* page the banner and the page disagree for everyone whose browser is set to
|
|
52
|
+
* anything else. Consent shown in a language the page is not in is a consent
|
|
53
|
+
* a regulator can question. No CMP on the page is a skip, not a pass.
|
|
54
|
+
*/
|
|
55
|
+
export declare function consentTag(html: string): Check;
|
|
56
|
+
//# sourceMappingURL=address-checks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"address-checks.d.ts","sourceRoot":"","sources":["../../src/doctor/address-checks.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAC5C,OAAO,EAQL,KAAK,GAAG,EACT,MAAM,SAAS,CAAC;AAEjB;;;;;;;;;GASG;AAEH,uFAAuF;AACvF,MAAM,WAAW,UAAU;IACzB,oEAAoE;IACpE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,EAAE,CAAC;CAChB;AAED,wBAAsB,sBAAsB,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,UAAU,EAAE,GAAG,OAAO,CAAC,KAAK,CAAC,CA4CzF;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAMlD;AAED,6GAA6G;AAC7G,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,EAAE,CAQ7E;AAED;;;;;;;;GAQG;AACH,wBAAsB,eAAe,CACnC,GAAG,EAAE,GAAG,EACR,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EACZ,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC,KAAK,CAAC,CA+BhB;AA4CD,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,EAAE,CAAA;CAAE,EAAE,CAWvE;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAQlE;AAID;;;;;;;;;GASG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,KAAK,CAuC9C"}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.canonicalBothAddresses = canonicalBothAddresses;
|
|
4
|
+
exports.visibleMarkup = visibleMarkup;
|
|
5
|
+
exports.platformMentions = platformMentions;
|
|
6
|
+
exports.whiteLabelLeaks = whiteLabelLeaks;
|
|
7
|
+
exports.findCmps = findCmps;
|
|
8
|
+
exports.languageHints = languageHints;
|
|
9
|
+
exports.consentTag = consentTag;
|
|
10
|
+
const probe_1 = require("./probe");
|
|
11
|
+
async function canonicalBothAddresses(get, sets) {
|
|
12
|
+
const title = 'Every address of a page names the same canonical';
|
|
13
|
+
const usable = sets.filter((s) => s.urls.length >= 2 || s.expected);
|
|
14
|
+
if (usable.length === 0)
|
|
15
|
+
return (0, probe_1.check)('canonical-both-addresses', title, 'skip', 'Only one address of this page is known. Pass --also <the other address>, or --site <slug> to have them looked up.', '');
|
|
16
|
+
const bad = [];
|
|
17
|
+
const good = [];
|
|
18
|
+
for (const set of usable) {
|
|
19
|
+
const before = bad.length;
|
|
20
|
+
const seen = [];
|
|
21
|
+
for (const url of [...new Set(set.urls)]) {
|
|
22
|
+
const { hops, final } = await (0, probe_1.follow)(get, url, { accept: 'text/html' });
|
|
23
|
+
const landed = hops.length > 1 && final.ok ? final.url : url;
|
|
24
|
+
if (!final.ok || final.status !== 200) {
|
|
25
|
+
bad.push(`${url} → ${(0, probe_1.describeProbe)(final)}`);
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
seen.push({ url, final: landed, canonical: (0, probe_1.canonicalOf)(final.text, landed) });
|
|
29
|
+
}
|
|
30
|
+
const expected = set.expected ?? seen.find((s) => s.canonical)?.canonical;
|
|
31
|
+
for (const s of seen) {
|
|
32
|
+
const via = s.final !== s.url ? ` (via ${s.final})` : '';
|
|
33
|
+
if (!s.canonical)
|
|
34
|
+
bad.push(`${s.url}${via} declares no canonical`);
|
|
35
|
+
else if (expected && !(0, probe_1.sameUrl)(s.canonical, expected))
|
|
36
|
+
bad.push(`${s.url}${via} names ${s.canonical}, expected ${expected}`);
|
|
37
|
+
}
|
|
38
|
+
if (seen.length && bad.length === before)
|
|
39
|
+
good.push(`${seen.map((s) => s.url).join(' and ')} → ${expected}`);
|
|
40
|
+
}
|
|
41
|
+
return (0, probe_1.check)('canonical-both-addresses', title, bad.length ? 'fail' : 'pass', bad.length ? (0, probe_1.some)(bad) : (0, probe_1.some)(good), bad.length
|
|
42
|
+
? 'Two copies with different canonicals compete in search, and the platform copy can outrank the customer’s own domain.'
|
|
43
|
+
: '');
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Strip what a browser does not show and a reader cannot click: scripts,
|
|
47
|
+
* styles, and `<link>` tags (preloads, stylesheets). A white-label site may
|
|
48
|
+
* well load its JavaScript from the platform's CDN — that is infrastructure,
|
|
49
|
+
* not branding — so only the markup a person sees, and the links they follow,
|
|
50
|
+
* are judged.
|
|
51
|
+
*/
|
|
52
|
+
function visibleMarkup(html) {
|
|
53
|
+
return html
|
|
54
|
+
.replace(/<script\b[\s\S]*?<\/script>/gi, '')
|
|
55
|
+
.replace(/<style\b[\s\S]*?<\/style>/gi, '')
|
|
56
|
+
.replace(/<link\b[^>]*>/gi, '')
|
|
57
|
+
.replace(/<meta\b[^>]*>/gi, '');
|
|
58
|
+
}
|
|
59
|
+
/** Platform URLs with a path: a bare-origin link is a "powered by" badge, which is a setting, not a leak. */
|
|
60
|
+
function platformMentions(text, platformHost) {
|
|
61
|
+
const host = platformHost.replace(/[.]/g, '\\.');
|
|
62
|
+
const re = new RegExp(`https?://(?:www\\.)?${host}(/[^\\s"'<>)\\]\`]+)`, 'gi');
|
|
63
|
+
return [
|
|
64
|
+
...new Set([...text.matchAll(re)].map((m) => m[0]).filter((u) => !/^https?:\/\/[^/]+\/?$/.test(u))),
|
|
65
|
+
];
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* `white-label-leaks`: a page on the customer's domain does not send its
|
|
69
|
+
* readers, or its agents, back to the platform's host.
|
|
70
|
+
*
|
|
71
|
+
* The Markdown twin of a reference page on a white-label domain ended with a
|
|
72
|
+
* "For AI agents" block pointing at the platform's API host for llms-full.txt
|
|
73
|
+
* and MCP, while the same files existed on the customer's domain (FMU-B15).
|
|
74
|
+
* The HTML looked clean; the agent-facing copy was the leak. Both are read.
|
|
75
|
+
*/
|
|
76
|
+
async function whiteLabelLeaks(get, pageUrl, html, platformHost) {
|
|
77
|
+
const title = 'No platform URLs on a custom-domain page';
|
|
78
|
+
const host = (0, probe_1.hostOf)(pageUrl);
|
|
79
|
+
if (!host || host === platformHost.toLowerCase() || host === `www.${platformHost.toLowerCase()}`)
|
|
80
|
+
return (0, probe_1.check)('white-label-leaks', title, 'skip', `${pageUrl} is on the platform host itself.`, '');
|
|
81
|
+
const leaks = platformMentions(visibleMarkup(html), platformHost).map((u) => `HTML: ${u}`);
|
|
82
|
+
const u = new URL(pageUrl);
|
|
83
|
+
const mdUrl = `${u.origin}${u.pathname.replace(/\/+$/, '')}.md`;
|
|
84
|
+
const md = await get(mdUrl, { accept: 'text/markdown,text/plain' });
|
|
85
|
+
const isMd = md.ok && md.status === 200 && !/text\/html/i.test(md.headers.get('content-type') ?? '');
|
|
86
|
+
if (isMd)
|
|
87
|
+
for (const m of platformMentions(md.text, platformHost))
|
|
88
|
+
leaks.push(`${mdUrl}: ${m}`);
|
|
89
|
+
return (0, probe_1.check)('white-label-leaks', title, leaks.length ? 'fail' : 'pass', leaks.length
|
|
90
|
+
? (0, probe_1.some)(leaks)
|
|
91
|
+
: `No ${platformHost} links in the page${isMd ? ' or its Markdown twin' : ''}.`, leaks.length
|
|
92
|
+
? `A white-label site that links readers or agents to ${platformHost} exposes the platform behind it and splits its traffic; serve the same resources under the customer's domain.`
|
|
93
|
+
: '');
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Consent managers, and where each one is told which language to speak.
|
|
97
|
+
*
|
|
98
|
+
* Matched on the vendor's loader host or global, so a CMP is recognised
|
|
99
|
+
* whether it is a `<script>` in the HTML, a preload of a script a framework
|
|
100
|
+
* injects later, or a config object in a serialized payload. The language
|
|
101
|
+
* hints are each vendor's documented attribute or query parameter; a vendor
|
|
102
|
+
* not listed here is still found by the generic hints below.
|
|
103
|
+
*/
|
|
104
|
+
const CMPS = [
|
|
105
|
+
{ name: 'Cookie Munch', match: /cookiemunch\.(?:net|com)/i },
|
|
106
|
+
{ name: 'Cookiebot', match: /consent\.cookiebot\.(?:com|eu)|uc\.js/i },
|
|
107
|
+
{ name: 'OneTrust', match: /otSDKStub|cdn\.cookielaw\.org|onetrust\.com/i },
|
|
108
|
+
{ name: 'Osano', match: /cmp\.osano\.com/i },
|
|
109
|
+
{ name: 'Termly', match: /app\.termly\.io/i },
|
|
110
|
+
{ name: 'Usercentrics', match: /usercentrics\.eu/i },
|
|
111
|
+
{ name: 'Didomi', match: /sdk\.privacy-center\.org|didomi/i },
|
|
112
|
+
{ name: 'iubenda', match: /cdn\.iubenda\.com/i },
|
|
113
|
+
{ name: 'Complianz', match: /complianz/i },
|
|
114
|
+
{ name: 'TCF CMP', match: /__tcfapi/ },
|
|
115
|
+
];
|
|
116
|
+
/**
|
|
117
|
+
* Where a CMP's language is stated, across vendors: `data-culture` (Cookie
|
|
118
|
+
* Munch, Cookiebot), `data-language` (OneTrust's `data-document-language`,
|
|
119
|
+
* Usercentrics), `data-lang`, a `culture`/`language` key in a serialized
|
|
120
|
+
* config, or a `?culture=`/`?language=`/`?lang=`/`?hl=` loader parameter.
|
|
121
|
+
*/
|
|
122
|
+
const LANGUAGE_HINTS = [
|
|
123
|
+
/data-(?:document-)?(?:culture|language|lang)\s*=\s*\\?["']([A-Za-z]{2,3}(?:[-_][A-Za-z0-9]{2,8})?)/g,
|
|
124
|
+
/\\?["'](?:culture|language|lang|uiLanguage)\\?["']\s*:\s*\\?["']([A-Za-z]{2,3}(?:[-_][A-Za-z0-9]{2,8})?)/g,
|
|
125
|
+
/[?&](?:culture|language|lang|hl)=([A-Za-z]{2,3}(?:[-_][A-Za-z0-9]{2,8})?)/g,
|
|
126
|
+
];
|
|
127
|
+
/**
|
|
128
|
+
* How far around a vendor mention a language hint still belongs to it. Small
|
|
129
|
+
* on purpose: a framework's serialized payload also carries the PAGE's
|
|
130
|
+
* language a few kilobytes away, and reading that as the banner's would pass
|
|
131
|
+
* exactly the page this check exists to fail.
|
|
132
|
+
*/
|
|
133
|
+
const HINT_WINDOW = 600;
|
|
134
|
+
function findCmps(html) {
|
|
135
|
+
const out = [];
|
|
136
|
+
for (const cmp of CMPS) {
|
|
137
|
+
const re = new RegExp(cmp.match.source, cmp.match.flags.includes('g') ? cmp.match.flags : `${cmp.match.flags}g`);
|
|
138
|
+
const at = [...html.matchAll(re)].map((m) => m.index ?? 0);
|
|
139
|
+
if (at.length)
|
|
140
|
+
out.push({ name: cmp.name, at });
|
|
141
|
+
}
|
|
142
|
+
return out;
|
|
143
|
+
}
|
|
144
|
+
function languageHints(html, at) {
|
|
145
|
+
const found = new Set();
|
|
146
|
+
for (const i of at) {
|
|
147
|
+
const win = html.slice(Math.max(0, i - HINT_WINDOW), i + HINT_WINDOW);
|
|
148
|
+
for (const re of LANGUAGE_HINTS)
|
|
149
|
+
for (const m of win.matchAll(new RegExp(re.source, re.flags)))
|
|
150
|
+
found.add(m[1].toLowerCase());
|
|
151
|
+
}
|
|
152
|
+
return [...found];
|
|
153
|
+
}
|
|
154
|
+
const primary = (tag) => tag.toLowerCase().split(/[-_]/)[0];
|
|
155
|
+
/**
|
|
156
|
+
* `consent-tag`: the cookie banner speaks the page's language.
|
|
157
|
+
*
|
|
158
|
+
* The CMP tag carried no culture, so the banner followed the BROWSER's
|
|
159
|
+
* language rather than the page's (FMU-B17, S05): a German reader on the
|
|
160
|
+
* English page got a German banner over English text, and on a translated
|
|
161
|
+
* page the banner and the page disagree for everyone whose browser is set to
|
|
162
|
+
* anything else. Consent shown in a language the page is not in is a consent
|
|
163
|
+
* a regulator can question. No CMP on the page is a skip, not a pass.
|
|
164
|
+
*/
|
|
165
|
+
function consentTag(html) {
|
|
166
|
+
const title = 'The consent banner is told the page language';
|
|
167
|
+
const cmps = findCmps(html);
|
|
168
|
+
if (cmps.length === 0)
|
|
169
|
+
return (0, probe_1.check)('consent-tag', title, 'skip', 'No consent manager found on this page.', '');
|
|
170
|
+
const lang = /<html\b[^>]*\blang\s*=\s*["']?([A-Za-z-]+)/i.exec(html)?.[1]?.toLowerCase();
|
|
171
|
+
const names = cmps.map((c) => c.name).join(', ');
|
|
172
|
+
const matters = 'A banner that follows the browser rather than the page asks for consent in a language the page is not written in.';
|
|
173
|
+
if (!lang)
|
|
174
|
+
return (0, probe_1.check)('consent-tag', title, 'fail', `${names} found, but the page declares no <html lang>.`, matters);
|
|
175
|
+
const hints = languageHints(html, cmps.flatMap((c) => c.at));
|
|
176
|
+
if (hints.length === 0)
|
|
177
|
+
return (0, probe_1.check)('consent-tag', title, 'fail', `${names} is given no language (no data-culture / data-language / language parameter); the banner will follow the browser, while the page is "${lang}".`, matters);
|
|
178
|
+
const wrong = hints.filter((h) => primary(h) !== primary(lang));
|
|
179
|
+
return (0, probe_1.check)('consent-tag', title, wrong.length ? 'fail' : 'pass', wrong.length
|
|
180
|
+
? `${names} is told "${wrong.join(', ')}" on a "${lang}" page.`
|
|
181
|
+
: `${names} is told "${hints.join(', ')}", matching the page ("${lang}").`, wrong.length ? matters : '');
|
|
182
|
+
}
|
|
183
|
+
//# sourceMappingURL=address-checks.js.map
|