@vigilbase/cli 0.1.4 → 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 (191) hide show
  1. package/CLOUDFLARE.md +3 -4
  2. package/COMMANDS.md +158 -83
  3. package/LOCAL.md +721 -0
  4. package/ONE.md +2 -4
  5. package/README.md +341 -123
  6. package/SECURITY.md +2 -2
  7. package/dist/auth/device-auth.d.ts +0 -1
  8. package/dist/auth/device-auth.js +13 -8
  9. package/dist/auth/index.d.ts +4 -2
  10. package/dist/auth/index.js +3 -2
  11. package/dist/auth/provider.d.ts +18 -0
  12. package/dist/auth/provider.js +71 -0
  13. package/dist/auth/refresh.d.ts +29 -0
  14. package/dist/auth/refresh.js +199 -0
  15. package/dist/auth/store.d.ts +103 -4
  16. package/dist/auth/store.js +400 -74
  17. package/dist/auth/token-metadata.d.ts +9 -0
  18. package/dist/auth/token-metadata.js +21 -0
  19. package/dist/auth/types.d.ts +8 -2
  20. package/dist/auth/types.js +0 -1
  21. package/dist/bin.d.ts +0 -1
  22. package/dist/bin.js +0 -1
  23. package/dist/cli.d.ts +4 -2
  24. package/dist/cli.js +45 -15
  25. package/dist/commands/account.d.ts +0 -1
  26. package/dist/commands/account.js +0 -1
  27. package/dist/commands/auth.d.ts +0 -1
  28. package/dist/commands/auth.js +82 -22
  29. package/dist/commands/billing.d.ts +0 -1
  30. package/dist/commands/billing.js +0 -1
  31. package/dist/commands/cloudflare.d.ts +1 -2
  32. package/dist/commands/cloudflare.js +6 -6
  33. package/dist/commands/helpers.d.ts +15 -1
  34. package/dist/commands/helpers.js +2 -3
  35. package/dist/commands/index.d.ts +3 -2
  36. package/dist/commands/index.js +6 -2
  37. package/dist/commands/legacy-validation.d.ts +0 -1
  38. package/dist/commands/legacy-validation.js +0 -1
  39. package/dist/commands/local.d.ts +35 -0
  40. package/dist/commands/local.js +1543 -0
  41. package/dist/commands/one.d.ts +0 -1
  42. package/dist/commands/one.js +6 -7
  43. package/dist/commands/public-tools.d.ts +0 -1
  44. package/dist/commands/public-tools.js +55 -17
  45. package/dist/commands/runs.d.ts +0 -1
  46. package/dist/commands/runs.js +0 -1
  47. package/dist/commands/security.d.ts +1 -2
  48. package/dist/commands/security.js +1 -2
  49. package/dist/commands/watchdog.d.ts +0 -1
  50. package/dist/commands/watchdog.js +0 -1
  51. package/dist/index.d.ts +3 -3
  52. package/dist/index.js +1 -2
  53. package/dist/lib/banner.d.ts +0 -1
  54. package/dist/lib/banner.js +0 -1
  55. package/dist/lib/client.d.ts +4 -2
  56. package/dist/lib/client.js +42 -18
  57. package/dist/lib/cloudflare-client.d.ts +0 -1
  58. package/dist/lib/cloudflare-client.js +0 -1
  59. package/dist/lib/config.d.ts +31 -3
  60. package/dist/lib/config.js +72 -13
  61. package/dist/lib/format.d.ts +2 -1
  62. package/dist/lib/format.js +27 -7
  63. package/dist/lib/help.d.ts +1 -2
  64. package/dist/lib/help.js +37 -17
  65. package/dist/lib/network.d.ts +10 -1
  66. package/dist/lib/network.js +33 -5
  67. package/dist/lib/one-client.d.ts +5 -3
  68. package/dist/lib/one-client.js +49 -3
  69. package/dist/lib/one-operations.d.ts +0 -1
  70. package/dist/lib/one-operations.js +0 -1
  71. package/dist/lib/open-url.d.ts +0 -1
  72. package/dist/lib/open-url.js +0 -1
  73. package/dist/lib/platform.d.ts +2 -0
  74. package/dist/lib/platform.js +4 -0
  75. package/dist/lib/progress.d.ts +0 -1
  76. package/dist/lib/progress.js +0 -1
  77. package/dist/lib/security-client.d.ts +1 -2
  78. package/dist/lib/security-client.js +0 -1
  79. package/dist/lib/skill-fetch.d.ts +0 -1
  80. package/dist/lib/skill-fetch.js +0 -1
  81. package/dist/lib/system-trust.d.ts +0 -1
  82. package/dist/lib/system-trust.js +0 -1
  83. package/dist/lib/tui.d.ts +308 -0
  84. package/dist/lib/tui.js +846 -0
  85. package/dist/local/cloudflare-checkup/cf/allowlist.d.ts +94 -0
  86. package/dist/local/cloudflare-checkup/cf/allowlist.js +183 -0
  87. package/dist/local/cloudflare-checkup/cf/auth-lifecycle.d.ts +112 -0
  88. package/dist/local/cloudflare-checkup/cf/auth-lifecycle.js +312 -0
  89. package/dist/local/cloudflare-checkup/cf/child-env.d.ts +31 -0
  90. package/dist/local/cloudflare-checkup/cf/child-env.js +80 -0
  91. package/dist/local/cloudflare-checkup/cf/install.d.ts +105 -0
  92. package/dist/local/cloudflare-checkup/cf/install.js +308 -0
  93. package/dist/local/cloudflare-checkup/cf/locate.d.ts +43 -0
  94. package/dist/local/cloudflare-checkup/cf/locate.js +227 -0
  95. package/dist/local/cloudflare-checkup/cf/run-cf.d.ts +88 -0
  96. package/dist/local/cloudflare-checkup/cf/run-cf.js +274 -0
  97. package/dist/local/cloudflare-checkup/collector.d.ts +47 -0
  98. package/dist/local/cloudflare-checkup/collector.js +295 -0
  99. package/dist/local/cloudflare-checkup/contract.d.ts +2804 -0
  100. package/dist/local/cloudflare-checkup/contract.js +1620 -0
  101. package/dist/local/cloudflare-checkup/email-verification.d.ts +124 -0
  102. package/dist/local/cloudflare-checkup/email-verification.js +211 -0
  103. package/dist/local/cloudflare-checkup/errors.d.ts +29 -0
  104. package/dist/local/cloudflare-checkup/errors.js +103 -0
  105. package/dist/local/cloudflare-checkup/output.d.ts +23 -0
  106. package/dist/local/cloudflare-checkup/output.js +88 -0
  107. package/dist/local/cloudflare-checkup/prompts.d.ts +40 -0
  108. package/dist/local/cloudflare-checkup/prompts.js +139 -0
  109. package/dist/local/cloudflare-checkup/rate-budget.d.ts +41 -0
  110. package/dist/local/cloudflare-checkup/rate-budget.js +79 -0
  111. package/dist/local/cloudflare-checkup/snapshot-file.d.ts +52 -0
  112. package/dist/local/cloudflare-checkup/snapshot-file.js +185 -0
  113. package/dist/local/cloudflare-checkup/submit-client.d.ts +112 -0
  114. package/dist/local/cloudflare-checkup/submit-client.js +395 -0
  115. package/dist/local/cloudflare-checkup/terminal-ui.d.ts +170 -0
  116. package/dist/local/cloudflare-checkup/terminal-ui.js +403 -0
  117. package/dist/types.d.ts +25 -3
  118. package/dist/types.js +16 -1
  119. package/dist/version.d.ts +0 -1
  120. package/dist/version.js +0 -1
  121. package/package.json +12 -12
  122. package/dist/auth/device-auth.d.ts.map +0 -1
  123. package/dist/auth/device-auth.js.map +0 -1
  124. package/dist/auth/index.d.ts.map +0 -1
  125. package/dist/auth/index.js.map +0 -1
  126. package/dist/auth/store.d.ts.map +0 -1
  127. package/dist/auth/store.js.map +0 -1
  128. package/dist/auth/types.d.ts.map +0 -1
  129. package/dist/auth/types.js.map +0 -1
  130. package/dist/bin.d.ts.map +0 -1
  131. package/dist/bin.js.map +0 -1
  132. package/dist/cli.d.ts.map +0 -1
  133. package/dist/cli.js.map +0 -1
  134. package/dist/commands/account.d.ts.map +0 -1
  135. package/dist/commands/account.js.map +0 -1
  136. package/dist/commands/auth.d.ts.map +0 -1
  137. package/dist/commands/auth.js.map +0 -1
  138. package/dist/commands/billing.d.ts.map +0 -1
  139. package/dist/commands/billing.js.map +0 -1
  140. package/dist/commands/cloudflare.d.ts.map +0 -1
  141. package/dist/commands/cloudflare.js.map +0 -1
  142. package/dist/commands/helpers.d.ts.map +0 -1
  143. package/dist/commands/helpers.js.map +0 -1
  144. package/dist/commands/index.d.ts.map +0 -1
  145. package/dist/commands/index.js.map +0 -1
  146. package/dist/commands/legacy-validation.d.ts.map +0 -1
  147. package/dist/commands/legacy-validation.js.map +0 -1
  148. package/dist/commands/one.d.ts.map +0 -1
  149. package/dist/commands/one.js.map +0 -1
  150. package/dist/commands/public-tools.d.ts.map +0 -1
  151. package/dist/commands/public-tools.js.map +0 -1
  152. package/dist/commands/runs.d.ts.map +0 -1
  153. package/dist/commands/runs.js.map +0 -1
  154. package/dist/commands/security.d.ts.map +0 -1
  155. package/dist/commands/security.js.map +0 -1
  156. package/dist/commands/watchdog.d.ts.map +0 -1
  157. package/dist/commands/watchdog.js.map +0 -1
  158. package/dist/index.d.ts.map +0 -1
  159. package/dist/index.js.map +0 -1
  160. package/dist/lib/banner.d.ts.map +0 -1
  161. package/dist/lib/banner.js.map +0 -1
  162. package/dist/lib/client.d.ts.map +0 -1
  163. package/dist/lib/client.js.map +0 -1
  164. package/dist/lib/cloudflare-client.d.ts.map +0 -1
  165. package/dist/lib/cloudflare-client.js.map +0 -1
  166. package/dist/lib/config.d.ts.map +0 -1
  167. package/dist/lib/config.js.map +0 -1
  168. package/dist/lib/format.d.ts.map +0 -1
  169. package/dist/lib/format.js.map +0 -1
  170. package/dist/lib/help.d.ts.map +0 -1
  171. package/dist/lib/help.js.map +0 -1
  172. package/dist/lib/network.d.ts.map +0 -1
  173. package/dist/lib/network.js.map +0 -1
  174. package/dist/lib/one-client.d.ts.map +0 -1
  175. package/dist/lib/one-client.js.map +0 -1
  176. package/dist/lib/one-operations.d.ts.map +0 -1
  177. package/dist/lib/one-operations.js.map +0 -1
  178. package/dist/lib/open-url.d.ts.map +0 -1
  179. package/dist/lib/open-url.js.map +0 -1
  180. package/dist/lib/progress.d.ts.map +0 -1
  181. package/dist/lib/progress.js.map +0 -1
  182. package/dist/lib/security-client.d.ts.map +0 -1
  183. package/dist/lib/security-client.js.map +0 -1
  184. package/dist/lib/skill-fetch.d.ts.map +0 -1
  185. package/dist/lib/skill-fetch.js.map +0 -1
  186. package/dist/lib/system-trust.d.ts.map +0 -1
  187. package/dist/lib/system-trust.js.map +0 -1
  188. package/dist/types.d.ts.map +0 -1
  189. package/dist/types.js.map +0 -1
  190. package/dist/version.d.ts.map +0 -1
  191. package/dist/version.js.map +0 -1
@@ -0,0 +1,94 @@
1
+ /**
2
+ * The frozen allowlist of read-only cf command templates (spec §F4).
3
+ *
4
+ * argv only ever comes from these templates. Interpolated values are
5
+ * re-validated (32-hex IDs, the 19 zone setting IDs, integer pages), every API
6
+ * template carries `--quiet` and profile mode appends `--profile <p>`. No
7
+ * template has a confirmation-skipping, local-mode or request-body flag;
8
+ * `run-cf.ts` refuses such argv at spawn time as a second guard. `per_page`
9
+ * values equal the scanner's, so page boundaries and API order match live runs.
10
+ *
11
+ * The offline preflight runs each template with `--dry-run` and placeholder
12
+ * IDs. In cf beta.13 the dry-run object is a literal emitted by each generated
13
+ * handler, so the preflight is a command-name → API-path mapping check only:
14
+ * it detects renamed or remapped commands but does not prove the real request
15
+ * target. Scrubbing base-URL variables from the child env is that control.
16
+ */
17
+ import { type CollectableKind, type ZoneSettingId } from '../contract.js';
18
+ export declare const DEFAULT_PROFILE = "vigilbase-checkup";
19
+ /** Template IDs: every collectable kind plus account selection (`accounts list`). */
20
+ export type TemplateId = CollectableKind | 'account_selection';
21
+ export type Paging = {
22
+ kind: 'single';
23
+ }
24
+ /** `--page N` until an empty page; `maxPages` data pages. */
25
+ | {
26
+ kind: 'pages';
27
+ perPage: number;
28
+ maxPages: number;
29
+ }
30
+ /** Cursor-only list printed without its cursor: one call, `possibly_truncated` at the threshold. */
31
+ | {
32
+ kind: 'one_call';
33
+ perPage: number;
34
+ };
35
+ export type TemplateIds = {
36
+ accountId?: string;
37
+ zoneId?: string;
38
+ rulesetId?: string;
39
+ settingId?: ZoneSettingId;
40
+ page?: number;
41
+ };
42
+ type TemplateSpec = {
43
+ /** Command words, then fixed arguments; `{Z}`, `{R}`, `{S}` and `{N}` are interpolated. */
44
+ args: readonly string[];
45
+ /** Expected dry-run path below `https://api.cloudflare.com/client/v4`; `{A}` is the account. */
46
+ path: string;
47
+ /** Expected dry-run query; `{A}`/`{N}` are interpolated. Absent when cf prints none. */
48
+ query?: Readonly<Record<string, string | number | boolean>>;
49
+ paging: Paging;
50
+ output: 'array' | 'object';
51
+ scope: 'account' | 'zone' | 'setting' | 'ruleset';
52
+ };
53
+ export declare const TEMPLATES: Readonly<Record<TemplateId, TemplateSpec>>;
54
+ export declare const TEMPLATE_IDS: TemplateId[];
55
+ export type ProfileArgs = {
56
+ authMode: 'profile' | 'env' | 'default';
57
+ profile: string;
58
+ };
59
+ export declare function validateProfileName(profile: string): string;
60
+ /** The exact argv for one call. Throws (a programming error) on any invalid interpolation. */
61
+ export declare function buildArgv(id: TemplateId, ids: TemplateIds, auth: ProfileArgs, options?: {
62
+ dryRun?: boolean;
63
+ }): string[];
64
+ export type PreflightCheck = {
65
+ id: TemplateId;
66
+ settingId?: ZoneSettingId;
67
+ argv: string[];
68
+ expectedPath: string;
69
+ expectedQuery?: Record<string, unknown>;
70
+ };
71
+ /** One dry-run per template (one per zone setting), with placeholder IDs. */
72
+ export declare function preflightChecks(auth: ProfileArgs, only?: readonly TemplateId[]): PreflightCheck[];
73
+ export declare const PREFLIGHT_ACCOUNT_ID: string;
74
+ /** True when a dry-run object maps to `GET` and exactly the expected path and query. */
75
+ export declare function dryRunMatches(stdout: string, check: PreflightCheck): boolean;
76
+ /** Kinds whose mismatch aborts collection with CF_ALLOWLIST_MISMATCH. */
77
+ export declare const CRITICAL_TEMPLATES: readonly TemplateId[];
78
+ /** Display form of every template, with placeholders, for `collect --dry-run`. */
79
+ export declare function templateDisplay(id: TemplateId, auth: ProfileArgs): string[];
80
+ /** The other cf argv the CLI uses: version, the serial whoami, and the login and logout commands. */
81
+ export declare function versionArgv(): string[];
82
+ export declare function whoamiArgv(auth: ProfileArgs): string[];
83
+ /** `cf auth create <p> [--no-browser] --scopes …` (re-authenticates an existing profile). */
84
+ export declare function loginArgv(profile: string, scopes: readonly string[], options: {
85
+ noBrowser: boolean;
86
+ }): string[];
87
+ /**
88
+ * `cf auth delete <p>` (logout). In cf 1.0.0-beta.13 it takes only the name:
89
+ * it refuses `--profile` and a shell API token, fails when the profile does
90
+ * not exist, removes the profile's directory bindings, logs the profile out
91
+ * and deletes its stored credentials. It has no confirmation prompt.
92
+ */
93
+ export declare function logoutArgv(profile: string): string[];
94
+ export {};
@@ -0,0 +1,183 @@
1
+ /**
2
+ * The frozen allowlist of read-only cf command templates (spec §F4).
3
+ *
4
+ * argv only ever comes from these templates. Interpolated values are
5
+ * re-validated (32-hex IDs, the 19 zone setting IDs, integer pages), every API
6
+ * template carries `--quiet` and profile mode appends `--profile <p>`. No
7
+ * template has a confirmation-skipping, local-mode or request-body flag;
8
+ * `run-cf.ts` refuses such argv at spawn time as a second guard. `per_page`
9
+ * values equal the scanner's, so page boundaries and API order match live runs.
10
+ *
11
+ * The offline preflight runs each template with `--dry-run` and placeholder
12
+ * IDs. In cf beta.13 the dry-run object is a literal emitted by each generated
13
+ * handler, so the preflight is a command-name → API-path mapping check only:
14
+ * it detects renamed or remapped commands but does not prove the real request
15
+ * target. Scrubbing base-URL variables from the child env is that control.
16
+ */
17
+ import { ZONE_SETTING_IDS } from '../contract.js';
18
+ const HEX32 = /^[0-9a-f]{32}$/;
19
+ const PLACEHOLDER_ID = '0'.repeat(32);
20
+ // Spec: ^[A-Za-z0-9_-]{1,64}$, minus a leading '-' so a name can never be parsed by cf as a flag.
21
+ const PROFILE = /^[A-Za-z0-9_][A-Za-z0-9_-]{0,63}$/;
22
+ export const DEFAULT_PROFILE = 'vigilbase-checkup';
23
+ const pages = (perPage, maxPages = 50) => ({ kind: 'pages', perPage, maxPages });
24
+ function deepFreeze(value) {
25
+ if (value && typeof value === 'object' && !Object.isFrozen(value)) {
26
+ Object.freeze(value);
27
+ for (const child of Object.values(value))
28
+ deepFreeze(child);
29
+ }
30
+ return value;
31
+ }
32
+ export const TEMPLATES = deepFreeze({
33
+ account_selection: { args: ['accounts', 'list', '--per-page', '50', '--page', '{N}'], path: '/accounts', query: { page: '{N}', per_page: 50 }, paging: pages(50), output: 'array', scope: 'account' },
34
+ account: { args: ['accounts', 'get'], path: '/accounts/{A}', paging: { kind: 'single' }, output: 'object', scope: 'account' },
35
+ zones: { args: ['zones', 'list', '--account-id', '{A}', '--per-page', '50', '--page', '{N}'], path: '/zones', query: { 'account.id': '{A}', page: '{N}', per_page: 50 }, paging: pages(50), output: 'array', scope: 'account' },
36
+ account_members: { args: ['accounts', 'members', 'list', '--per-page', '50', '--page', '{N}'], path: '/accounts/{A}/members', query: { page: '{N}', per_page: 50 }, paging: pages(50), output: 'array', scope: 'account' },
37
+ account_rulesets: { args: ['rulesets', 'list', '--per-page', '100'], path: '/accounts/{A}/rulesets', query: { per_page: 100 }, paging: { kind: 'one_call', perPage: 100 }, output: 'array', scope: 'account' },
38
+ account_access_rules: { args: ['firewall', 'access-rules', 'list', '--per-page', '50', '--page', '{N}'], path: '/accounts/{A}/firewall/access_rules/rules', query: { page: '{N}', per_page: 50 }, paging: pages(50), output: 'array', scope: 'account' },
39
+ access_apps: { args: ['zero-trust', 'access', 'applications', 'list', '--per-page', '50', '--page', '{N}'], path: '/accounts/{A}/access/apps', query: { page: '{N}', per_page: 50 }, paging: pages(50), output: 'array', scope: 'account' },
40
+ access_identity_providers: { args: ['zero-trust', 'identity-providers', 'list', '--per-page', '50', '--page', '{N}'], path: '/accounts/{A}/access/identity_providers', query: { page: '{N}', per_page: 50 }, paging: pages(50), output: 'array', scope: 'account' },
41
+ access_service_tokens: { args: ['zero-trust', 'access', 'service-tokens', 'list', '--per-page', '50', '--page', '{N}'], path: '/accounts/{A}/access/service_tokens', query: { page: '{N}', per_page: 50 }, paging: pages(50), output: 'array', scope: 'account' },
42
+ gateway_rules: { args: ['zero-trust', 'gateway', 'rules', 'list'], path: '/accounts/{A}/gateway/rules', query: {}, paging: { kind: 'single' }, output: 'array', scope: 'account' },
43
+ gateway_locations: { args: ['zero-trust', 'gateway', 'locations', 'list'], path: '/accounts/{A}/gateway/locations', query: {}, paging: { kind: 'single' }, output: 'array', scope: 'account' },
44
+ // Substitute for GET /cfd_tunnel (no cf command in beta.13); response equivalence is launch gate 1.
45
+ cfd_tunnels: { args: ['tunnels', 'list', '--tun-types', 'cfd_tunnel', '--is-deleted', 'false', '--per-page', '50', '--page', '{N}'], path: '/accounts/{A}/tunnels', query: { is_deleted: false, tun_types: 'cfd_tunnel', per_page: 50, page: '{N}' }, paging: pages(50), output: 'array', scope: 'account' },
46
+ zone_setting: { args: ['zones', 'settings', 'get', '{S}', '--zone', '{Z}'], path: '/zones/{Z}/settings/{S}', paging: { kind: 'single' }, output: 'object', scope: 'setting' },
47
+ zone_dnssec: { args: ['dns', 'dnssec', 'get', '--zone', '{Z}'], path: '/zones/{Z}/dnssec', paging: { kind: 'single' }, output: 'object', scope: 'zone' },
48
+ zone_dns_records: { args: ['dns', 'records', 'list', '--zone', '{Z}', '--per-page', '100', '--page', '{N}'], path: '/zones/{Z}/dns_records', query: { page: '{N}', per_page: 100 }, paging: pages(100, 100), output: 'array', scope: 'zone' },
49
+ zone_universal_ssl: { args: ['ssl', 'universal', 'settings', 'get', '--zone', '{Z}'], path: '/zones/{Z}/ssl/universal/settings', paging: { kind: 'single' }, output: 'object', scope: 'zone' },
50
+ zone_total_tls: { args: ['acm', 'total-tls', 'get', '--zone', '{Z}'], path: '/zones/{Z}/acm/total_tls', paging: { kind: 'single' }, output: 'object', scope: 'zone' },
51
+ zone_rulesets: { args: ['rulesets', 'list', '--zone', '{Z}', '--per-page', '100'], path: '/zones/{Z}/rulesets', query: { per_page: 100 }, paging: { kind: 'one_call', perPage: 100 }, output: 'array', scope: 'zone' },
52
+ zone_ruleset: { args: ['rulesets', 'get', '{R}', '--zone', '{Z}'], path: '/zones/{Z}/rulesets/{R}', paging: { kind: 'single' }, output: 'object', scope: 'ruleset' },
53
+ zone_access_rules: { args: ['firewall', 'access-rules', 'list', '--zone', '{Z}', '--per-page', '100', '--page', '{N}'], path: '/zones/{Z}/firewall/access_rules/rules', query: { page: '{N}', per_page: 100 }, paging: pages(100), output: 'array', scope: 'zone' },
54
+ zone_page_rules: { args: ['page-rules', 'list', '--zone', '{Z}'], path: '/zones/{Z}/pagerules', query: {}, paging: { kind: 'single' }, output: 'array', scope: 'zone' },
55
+ zone_bot_management: { args: ['bot-management', 'get', '--zone', '{Z}'], path: '/zones/{Z}/bot_management', paging: { kind: 'single' }, output: 'object', scope: 'zone' },
56
+ zone_page_shield: { args: ['client-side-security', 'settings', 'get', '--zone', '{Z}'], path: '/zones/{Z}/page_shield', paging: { kind: 'single' }, output: 'object', scope: 'zone' },
57
+ zone_schema_validation_settings: { args: ['api-security', 'schema-validation', 'settings', 'get', '--zone', '{Z}'], path: '/zones/{Z}/schema_validation/settings', paging: { kind: 'single' }, output: 'object', scope: 'zone' },
58
+ zone_schema_validation_schemas: { args: ['api-security', 'schema-validation', 'schemas', 'list', '--zone', '{Z}', '--omit-source', '--per-page', '100', '--page', '{N}'], path: '/zones/{Z}/schema_validation/schemas', query: { page: '{N}', per_page: 100, omit_source: true }, paging: pages(100), output: 'array', scope: 'zone' },
59
+ });
60
+ export const TEMPLATE_IDS = Object.keys(TEMPLATES);
61
+ export function validateProfileName(profile) {
62
+ if (!PROFILE.test(profile))
63
+ throw new Error('--profile must be 1-64 letters, digits, "_" or "-", and must not start with "-".');
64
+ return profile;
65
+ }
66
+ function hex(value, label) {
67
+ if (value === undefined || !HEX32.test(value))
68
+ throw new Error(`Invalid ${label} for a cf command.`);
69
+ return value;
70
+ }
71
+ /** The exact argv for one call. Throws (a programming error) on any invalid interpolation. */
72
+ export function buildArgv(id, ids, auth, options = {}) {
73
+ const spec = TEMPLATES[id];
74
+ const argv = spec.args.map((arg) => {
75
+ switch (arg) {
76
+ case '{Z}': return hex(ids.zoneId, 'zone ID');
77
+ case '{R}': return hex(ids.rulesetId, 'ruleset ID');
78
+ // Only `zones list --account-id`; every other template gets the account from CLOUDFLARE_ACCOUNT_ID.
79
+ case '{A}': return hex(ids.accountId, 'account ID');
80
+ case '{S}':
81
+ if (!ids.settingId || !ZONE_SETTING_IDS.includes(ids.settingId))
82
+ throw new Error('Invalid zone setting ID for a cf command.');
83
+ return ids.settingId;
84
+ case '{N}':
85
+ if (!Number.isSafeInteger(ids.page) || ids.page < 1 || ids.page > 1000)
86
+ throw new Error('Invalid page for a cf command.');
87
+ return String(ids.page);
88
+ default: return arg;
89
+ }
90
+ });
91
+ argv.push('--quiet');
92
+ if (auth.authMode === 'profile')
93
+ argv.push('--profile', validateProfileName(auth.profile));
94
+ if (options.dryRun)
95
+ argv.push('--dry-run');
96
+ return argv;
97
+ }
98
+ function fill(template, values) {
99
+ return template.replace(/\{([AZRSN])\}/g, (_m, key) => String(values[key]));
100
+ }
101
+ /** One dry-run per template (one per zone setting), with placeholder IDs. */
102
+ export function preflightChecks(auth, only) {
103
+ const values = { A: PLACEHOLDER_ID, Z: PLACEHOLDER_ID, R: PLACEHOLDER_ID, S: '', N: 1 };
104
+ const checks = [];
105
+ for (const id of only ?? TEMPLATE_IDS) {
106
+ const spec = TEMPLATES[id];
107
+ const settings = id === 'zone_setting' ? [...ZONE_SETTING_IDS] : [undefined];
108
+ for (const settingId of settings) {
109
+ const filled = { ...values, S: settingId ?? '' };
110
+ const query = spec.query === undefined ? undefined : Object.fromEntries(Object.entries(spec.query)
111
+ .map(([key, value]) => [key, typeof value === 'string' ? (value === '{N}' ? 1 : fill(value, filled)) : value]));
112
+ checks.push({
113
+ id,
114
+ ...(settingId ? { settingId } : {}),
115
+ argv: buildArgv(id, { accountId: PLACEHOLDER_ID, zoneId: PLACEHOLDER_ID, rulesetId: PLACEHOLDER_ID, settingId, page: 1 }, auth, { dryRun: true }),
116
+ expectedPath: fill(spec.path, filled),
117
+ ...(query ? { expectedQuery: query } : {}),
118
+ });
119
+ }
120
+ }
121
+ return checks;
122
+ }
123
+ export const PREFLIGHT_ACCOUNT_ID = PLACEHOLDER_ID;
124
+ const API_PREFIX = 'https://api.cloudflare.com/client/v4';
125
+ function sameQuery(actual, expected) {
126
+ if (expected === undefined)
127
+ return actual === undefined;
128
+ if (!actual || typeof actual !== 'object' || Array.isArray(actual))
129
+ return false;
130
+ const left = Object.entries(actual).filter(([, value]) => value !== undefined && value !== null);
131
+ if (left.length !== Object.keys(expected).length)
132
+ return false;
133
+ return left.every(([key, value]) => Object.hasOwn(expected, key) && expected[key] === value);
134
+ }
135
+ /** True when a dry-run object maps to `GET` and exactly the expected path and query. */
136
+ export function dryRunMatches(stdout, check) {
137
+ let printed;
138
+ try {
139
+ printed = JSON.parse(stdout);
140
+ }
141
+ catch {
142
+ return false;
143
+ }
144
+ if (!printed || typeof printed !== 'object' || Array.isArray(printed))
145
+ return false;
146
+ const { method, url, query } = printed;
147
+ if (method !== 'GET' || typeof url !== 'string' || !url.startsWith(`${API_PREFIX}/`))
148
+ return false;
149
+ return url.slice(API_PREFIX.length) === check.expectedPath && sameQuery(query, check.expectedQuery);
150
+ }
151
+ /** Kinds whose mismatch aborts collection with CF_ALLOWLIST_MISMATCH. */
152
+ export const CRITICAL_TEMPLATES = ['account_selection', 'account', 'zones'];
153
+ /** Display form of every template, with placeholders, for `collect --dry-run`. */
154
+ export function templateDisplay(id, auth) {
155
+ const argv = TEMPLATES[id].args.map((arg) => arg === '{Z}' ? '<ZONE_ID>' : arg === '{R}' ? '<RULESET_ID>' : arg === '{S}' ? '<SETTING_ID>' : arg === '{N}' ? '<PAGE>' : arg === '{A}' ? '<ACCOUNT_ID>' : arg);
156
+ argv.push('--quiet');
157
+ if (auth.authMode === 'profile')
158
+ argv.push('--profile', auth.profile);
159
+ return argv;
160
+ }
161
+ /** The other cf argv the CLI uses: version, the serial whoami, and the login and logout commands. */
162
+ export function versionArgv() {
163
+ return ['--version'];
164
+ }
165
+ export function whoamiArgv(auth) {
166
+ return auth.authMode === 'profile' ? ['auth', 'whoami', '--profile', validateProfileName(auth.profile)] : ['auth', 'whoami'];
167
+ }
168
+ /** `cf auth create <p> [--no-browser] --scopes …` (re-authenticates an existing profile). */
169
+ export function loginArgv(profile, scopes, options) {
170
+ for (const scope of scopes)
171
+ if (!/^[a-z0-9_.:-]{1,64}$/.test(scope))
172
+ throw new Error('Invalid OAuth scope.');
173
+ return ['auth', 'create', validateProfileName(profile), ...(options.noBrowser ? ['--no-browser'] : []), '--scopes', ...scopes];
174
+ }
175
+ /**
176
+ * `cf auth delete <p>` (logout). In cf 1.0.0-beta.13 it takes only the name:
177
+ * it refuses `--profile` and a shell API token, fails when the profile does
178
+ * not exist, removes the profile's directory bindings, logs the profile out
179
+ * and deletes its stored credentials. It has no confirmation prompt.
180
+ */
181
+ export function logoutArgv(profile) {
182
+ return ['auth', 'delete', validateProfileName(profile)];
183
+ }
@@ -0,0 +1,112 @@
1
+ import { LocalCheckupError } from '../errors.js';
2
+ import type { RequestBudget, Clock } from '../rate-budget.js';
3
+ import type { CfAuthMode } from './child-env.js';
4
+ import { type CfProcessResult } from './run-cf.js';
5
+ export declare const PRE_EXPIRY_MARGIN_MS = 60000;
6
+ export declare const RATE_LIMIT_PAUSE_MS = 30000;
7
+ export declare const TRANSIENT_RETRY_DELAYS_MS: readonly [1000, 3000];
8
+ export declare const MAX_CF_INVOCATIONS = 6000;
9
+ export type WhoamiSummary = {
10
+ authenticated: boolean;
11
+ /** ISO timestamp of the OAuth token expiry, when cf reports a parseable one. */
12
+ expiresAt?: string;
13
+ scopes?: string[];
14
+ };
15
+ /** Reduce cf's whoami JSON; email, account names and the credential file path are dropped. */
16
+ export declare function parseWhoami(stdout: string): WhoamiSummary | null;
17
+ export declare function isReadOnlyScope(scope: string): boolean;
18
+ export type DoctorAuth = {
19
+ source: 'cf_profile' | 'cf_env_token' | 'cf_default_profile';
20
+ profile: string | null;
21
+ authenticated: boolean;
22
+ expiresAt?: string;
23
+ scopes: 'known' | 'unknown';
24
+ missingRecommendedScopes?: string[];
25
+ writeScopeCount?: number;
26
+ };
27
+ export declare function authSourceFor(mode: CfAuthMode): DoctorAuth['source'];
28
+ /**
29
+ * Doctor's auth view. In env mode cf reports `scopes` and `expiresAt` from the
30
+ * active profile's stored OAuth file even while the API token is in use, so
31
+ * both are ignored: API token permissions cannot be introspected.
32
+ */
33
+ export declare function doctorAuth(summary: WhoamiSummary, mode: CfAuthMode, profile: string): DoctorAuth;
34
+ export declare function authRequiredError(mode: CfAuthMode, message?: string): LocalCheckupError;
35
+ export type CfCallFailure = {
36
+ kind: 'http';
37
+ status: number;
38
+ } | {
39
+ kind: 'timeout';
40
+ } | {
41
+ kind: 'output_too_large';
42
+ } | {
43
+ kind: 'command_unavailable';
44
+ } | {
45
+ kind: 'network';
46
+ } | {
47
+ kind: 'cf_error';
48
+ } | {
49
+ kind: 'auth';
50
+ };
51
+ export type CfCallOutcome = {
52
+ ok: true;
53
+ stdout: string;
54
+ } | {
55
+ ok: false;
56
+ failure: CfCallFailure;
57
+ };
58
+ export type CfCallOptions = {
59
+ stdoutCapBytes?: number;
60
+ /** Sets CLOUDFLARE_ACCOUNT_ID for the call; absent only for account selection. */
61
+ accountId?: string;
62
+ };
63
+ export type CfInvoke = (argv: readonly string[], options: CfCallOptions) => Promise<CfProcessResult>;
64
+ export type SchedulerOptions = {
65
+ invoke: CfInvoke;
66
+ /** One serial `cf auth whoami`; it is invoked only while dispatch is paused. */
67
+ whoami: () => Promise<CfProcessResult>;
68
+ budget: RequestBudget;
69
+ clock: Clock;
70
+ authMode: CfAuthMode;
71
+ /** Live children are killed when the run aborts. */
72
+ terminateAll?: () => Promise<void>;
73
+ maxInvocations?: number;
74
+ };
75
+ /** Dispatch gate shared by every worker. */
76
+ export declare class CfScheduler {
77
+ private readonly options;
78
+ invocations: number;
79
+ private dispatched;
80
+ private inFlight;
81
+ private drainWaiters;
82
+ private gate;
83
+ private refreshing;
84
+ private expiresAtMs;
85
+ private preExpiryEnabled;
86
+ /** Dispatch sequence number when the first 429 paused the run. */
87
+ private rateLimitPauseSeq;
88
+ private abortError;
89
+ constructor(options: SchedulerOptions);
90
+ get aborted(): Error | null;
91
+ /** The serial whoami (re-read once if it refreshed) before fan-out. Throws CF_AUTH_REQUIRED when cf is not signed in. */
92
+ start(): Promise<WhoamiSummary>;
93
+ /** Abort the run: no further dispatches, live children are killed. */
94
+ abort(error: Error): void;
95
+ throwIfAborted(): void;
96
+ /** One cf API call with the §F5 retry policy. */
97
+ call(argv: readonly string[], options?: CfCallOptions): Promise<CfCallOutcome>;
98
+ /** Wait for a request slot and an open gate; returns the dispatch sequence number. */
99
+ private dispatch;
100
+ private release;
101
+ private drain;
102
+ private shouldRefreshBeforeExpiry;
103
+ /** Close the dispatch gate until `work` (queued after any current gate) finishes. Failures abort the run. */
104
+ private setGate;
105
+ /** Pause dispatch, drain in-flight calls, run up to two serial whoami calls, resume. */
106
+ private refresh;
107
+ private expiryHasPassed;
108
+ private setExpiry;
109
+ private serialWhoami;
110
+ }
111
+ /** `null` for a clean exit; otherwise the classified failure. */
112
+ export declare function toFailure(result: CfProcessResult): CfCallFailure | null;