failproofai 0.0.14-beta.0 → 0.0.14-beta.2
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/.next/standalone/.codex/hooks.json +6 -6
- package/.next/standalone/.cursor/hooks.json +6 -6
- package/.next/standalone/.factory/hooks.json +9 -9
- package/.next/standalone/.next/BUILD_ID +1 -1
- package/.next/standalone/.next/build-manifest.json +5 -5
- package/.next/standalone/.next/prerender-manifest.json +3 -3
- package/.next/standalone/.next/required-server-files.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page/build-manifest.json +2 -2
- package/.next/standalone/.next/server/app/_global-error/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_global-error.html +1 -1
- package/.next/standalone/.next/server/app/_global-error.rsc +7 -7
- package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
- package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found/page/build-manifest.json +2 -2
- package/.next/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_not-found.html +1 -1
- package/.next/standalone/.next/server/app/_not-found.rsc +15 -15
- package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +15 -15
- package/.next/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +4 -4
- package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +10 -10
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/api/audit/invite/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/audit/run/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/login-request/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/login-verify/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/logout/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/reminder/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/status/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/audit/page/build-manifest.json +2 -2
- package/.next/standalone/.next/server/app/audit/page/server-reference-manifest.json +2 -2
- package/.next/standalone/.next/server/app/audit/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/audit/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/index.html +1 -1
- package/.next/standalone/.next/server/app/index.rsc +15 -15
- package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +15 -15
- package/.next/standalone/.next/server/app/index.segments/_head.segment.rsc +4 -4
- package/.next/standalone/.next/server/app/index.segments/_index.segment.rsc +10 -10
- package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/page/build-manifest.json +2 -2
- package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/policies/page/build-manifest.json +2 -2
- package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +8 -8
- package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page/build-manifest.json +2 -2
- package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/build-manifest.json +2 -2
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/projects/page/build-manifest.json +2 -2
- package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0--lkk6._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0-219ec._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__04_h-a5._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__06cuf1y._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__08w4wmd._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0g6bbuw._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0jnjf2t._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0q-v9z2._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0q0qzx1._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0q2nbsl._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0rv7m0k._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0xuaoik._.js +3 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__16le-kd._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__17g9wh7._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1ffkmds._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1fwl2mz._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1legmza._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1mrihkj._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1myjm-d._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1qb590j._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1qxztj-._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1shcqgr._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1xuedea._.js +1 -1
- package/.next/standalone/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_17k9e3w.js +4 -4
- package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_01r25oi._.js +1 -1
- package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_09z9-p7._.js +1 -1
- package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__00cz9as._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__015_i4t._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__030k0c6._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__03lv-pe._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__042d3cd._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__05r_17v._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__09q1168._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__07qfsfa._.js → [root-of-the-server]__09v9cwb._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0e2l3c1._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0h12me5._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0hci7t3._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0hdsupo._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0p84xee._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0qeuy9c._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0s89hoe._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0twi4fj._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0ynf7tx._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__11rtg6s._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__14dd6h8._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__170799-._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__17ai7sy._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1dky4g0._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1nq7ivq._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1tkjqka._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__212nf49._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_05whahf._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_1kje4fm._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_087bt9w._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_1fvisnp._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_209u41o._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpc._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/lib_0xkhw_v._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{node_modules_html-to-image_es_index_0g5r64e.js → node_modules_html-to-image_es_index_06xsmte.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_0-uvagv.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_03c7gi5.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_09p-8om.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_0v-kfiu.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_0xrgzyz.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_1806lsc.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_1j6dd-e.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_1sa65r-.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/node_modules_posthog-node_dist_entrypoints_index_node_mjs_11bnuzn._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_1ezd2jf._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_1tnuifj._.js +1 -1
- package/.next/standalone/.next/server/middleware-build-manifest.js +5 -5
- package/.next/standalone/.next/server/pages/404.html +1 -1
- package/.next/standalone/.next/server/pages/500.html +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.json +10 -10
- package/.next/standalone/.next/static/chunks/{13f1kmjea-0md.js → 02jcotghcxrbu.js} +1 -1
- package/.next/standalone/.next/static/chunks/{1374x9bv_s_vg.js → 0h-6ymo9v86b0.js} +2 -2
- package/.next/standalone/.next/static/chunks/0pndjhcxfu-0t.js +1 -0
- package/.next/standalone/.next/static/chunks/1-33f82_l32or.js +1 -0
- package/.next/standalone/.next/static/chunks/{003uz088wbmz7.js → 1bg_g3g359c_d.js} +1 -1
- package/.next/standalone/.next/static/chunks/{454dyizqik6t9.js → 1gg-8e9q_m__9.js} +2 -2
- package/.next/standalone/.next/static/chunks/{2ufuwhv03yvtb.js → 29zkom4s9de2d.js} +1 -1
- package/.next/standalone/.next/static/chunks/{17sdztgbcc0fi.js → 2llkdafnaejw_.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0iinhn4417nk1.js → 3-ogr79xqbw0h.js} +1 -1
- package/.next/standalone/.next/static/chunks/36j05kb2x323i.css +2 -0
- package/.next/standalone/.next/static/chunks/{43weu8f59tlix.js → 388hgfqye67xa.js} +1 -1
- package/.next/standalone/.next/static/chunks/396764jgd7-9j.js +1 -0
- package/.next/standalone/.next/static/chunks/3w7g0n0iwwnbs.js +1 -0
- package/.next/standalone/.next/static/chunks/{turbopack-0vavqo2d-tlt3.js → turbopack-10hj8t5pf33tu.js} +1 -1
- package/.next/standalone/.opencode/plugins/failproofai.mjs +30 -11
- package/.next/standalone/app/audit/_components/how-to-improve-section.tsx +11 -2
- package/.next/standalone/app/components/copy-button.tsx +22 -6
- package/.next/standalone/app/components/global-error-listeners.tsx +27 -0
- package/.next/standalone/components/reach-developers.tsx +1 -1
- package/.next/standalone/docker-hook-sync/Dockerfile +8 -4
- package/.next/standalone/integration-suite/Dockerfile +47 -0
- package/.next/standalone/integration-suite/README.md +87 -0
- package/.next/standalone/integration-suite/canary-policies.mjs +45 -0
- package/.next/standalone/integration-suite/capture-tokens.sh +48 -0
- package/.next/standalone/integration-suite/ci-entrypoint.sh +164 -0
- package/.next/standalone/integration-suite/inject-tokens.sh +44 -0
- package/.next/standalone/integration-suite/install-clis.sh +91 -0
- package/.next/standalone/integration-suite/probe-cli.sh +208 -0
- package/.next/standalone/integration-suite/report.js +74 -0
- package/.next/standalone/integration-suite/run.sh +107 -0
- package/.next/standalone/lib/error-origin.ts +67 -0
- package/.next/standalone/lib/format-duration.ts +23 -9
- package/.next/standalone/lib/install-check.ts +231 -0
- package/.next/standalone/package.json +1 -3
- package/.next/standalone/server.js +1 -1
- package/README.md +1 -17
- package/bin/failproofai.mjs +11 -1
- package/dist/cli.mjs +818 -320
- package/lib/error-origin.ts +67 -0
- package/lib/format-duration.ts +23 -9
- package/lib/install-check.ts +231 -0
- package/package.json +1 -3
- package/scripts/dev-hook.mjs +292 -0
- package/scripts/install-diagnosis.mjs +5 -8
- package/scripts/install-telemetry.mjs +12 -4
- package/scripts/translate-docs/cli.ts +53 -1
- package/scripts/translate-docs/mdx-translator.ts +158 -18
- package/scripts/translate-docs/readme-translator.ts +36 -18
- package/scripts/translate-docs/translator.ts +180 -9
- package/scripts/translate-docs/types.ts +6 -0
- package/scripts/translate-docs/validate-translation.ts +132 -0
- package/scripts/validate-mdx.ts +92 -15
- package/src/audit/cli.ts +53 -15
- package/src/auth/cli.ts +14 -5
- package/src/hooks/builtin-policies.ts +168 -50
- package/src/hooks/configure-wizard.ts +238 -22
- package/src/hooks/custom-hooks-loader.ts +70 -4
- package/src/hooks/handler.ts +4 -1
- package/src/hooks/policy-types.ts +8 -0
- package/src/hooks/tui.ts +96 -29
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0ge8e7w._.js +0 -3
- package/.next/standalone/.next/static/chunks/0405htbxay9vd.js +0 -1
- package/.next/standalone/.next/static/chunks/2m_tbl39fcedn.js +0 -1
- package/.next/standalone/.next/static/chunks/2o3ql0n5owqsw.js +0 -1
- package/.next/standalone/.next/static/chunks/35-90m9py64d4.js +0 -1
- package/.next/standalone/.next/static/chunks/3r--yyjkp9_7b.css +0 -2
- package/scripts/postinstall.mjs +0 -242
- package/scripts/preuninstall.mjs +0 -131
- /package/.next/standalone/.next/static/{MNJWFb2jLRrxRKGFNK6Hy → YKxI22hC-F_nHp7MEKfb1}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{MNJWFb2jLRrxRKGFNK6Hy → YKxI22hC-F_nHp7MEKfb1}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{MNJWFb2jLRrxRKGFNK6Hy → YKxI22hC-F_nHp7MEKfb1}/_ssgManifest.js +0 -0
|
@@ -43,4 +43,10 @@ export interface TranslationResult {
|
|
|
43
43
|
inputTokens: number;
|
|
44
44
|
outputTokens: number;
|
|
45
45
|
cached: boolean;
|
|
46
|
+
/**
|
|
47
|
+
* Total translation attempts spent (1 = valid on first try). Optional so the
|
|
48
|
+
* cached / dry-run result literals need no change; absent means "not
|
|
49
|
+
* translated this run" (cached) or "1".
|
|
50
|
+
*/
|
|
51
|
+
attempts?: number;
|
|
46
52
|
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generation-time validation for a freshly translated page.
|
|
3
|
+
*
|
|
4
|
+
* `findTranslationError` runs the exact checks the Mintlify deploy performs —
|
|
5
|
+
* frontmatter YAML then MDX body — plus one the deploy cannot: frontmatter
|
|
6
|
+
* KEY-PARITY against the English source. It is called on the exact bytes about
|
|
7
|
+
* to be written to disk (post-sanitize, post-link-rewrite), so a page that
|
|
8
|
+
* passes here is a page `mintlify validate` and the deploy accept.
|
|
9
|
+
*
|
|
10
|
+
* Why a translation-specific wrapper instead of `findPageError` alone:
|
|
11
|
+
* - Frontmatter YAML (class A): the model re-emits an inner `"` unescaped into
|
|
12
|
+
* a double-quoted `title:`/`description:` value, breaking the YAML. This is
|
|
13
|
+
* the class that failed run 29575781632; `findFrontmatterError` catches it.
|
|
14
|
+
* - Key parity (class B): the model drops the frontmatter block entirely, or
|
|
15
|
+
* renames a key. A dropped block is still *valid YAML* (mintlify tolerates
|
|
16
|
+
* it, deriving the title from the slug), so only comparing against the
|
|
17
|
+
* source's keys catches it. This is deliberately stricter than mintlify —
|
|
18
|
+
* the keys are prompt-forbidden to change, so it fails ~never but converts
|
|
19
|
+
* a silent content regression into a retry.
|
|
20
|
+
* - MDX body (classes C+): stray `<slug>`, `{#anchor}`, unmatched fence,
|
|
21
|
+
* swallowed `</div>` — `findMdxParseError` catches these.
|
|
22
|
+
*
|
|
23
|
+
* The message returned is written for a MODEL to act on: it names the failing
|
|
24
|
+
* construct and, for a body error, quotes the offending line, so the retry has
|
|
25
|
+
* a concrete defect to fix rather than a bare line number the RTL/README
|
|
26
|
+
* wrappers would have offset anyway.
|
|
27
|
+
*/
|
|
28
|
+
import { findFrontmatterError, findMdxParseError } from "../validate-mdx";
|
|
29
|
+
import YAML from "yaml";
|
|
30
|
+
|
|
31
|
+
// Same matcher as validate-mdx.ts; duplicated locally so this module owns its
|
|
32
|
+
// frontmatter extraction and does not depend on an internal export.
|
|
33
|
+
const FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---[ \t]*\r?\n?/;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* The sorted top-level keys of a page's frontmatter, or `null` when the page
|
|
37
|
+
* has no frontmatter block or its block is not a key/value mapping (an
|
|
38
|
+
* unparseable or scalar block). Callers distinguish "no block" from a YAML
|
|
39
|
+
* error separately via `findFrontmatterError`.
|
|
40
|
+
*/
|
|
41
|
+
function frontmatterKeys(page: string): string[] | null {
|
|
42
|
+
const match = FRONTMATTER_RE.exec(page);
|
|
43
|
+
if (!match) return null;
|
|
44
|
+
try {
|
|
45
|
+
const doc = YAML.parse(match[1]);
|
|
46
|
+
if (doc && typeof doc === "object" && !Array.isArray(doc)) {
|
|
47
|
+
return Object.keys(doc).sort();
|
|
48
|
+
}
|
|
49
|
+
return null;
|
|
50
|
+
} catch {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* A ±2-line window around `line` (1-based), the failing line prefixed `> ` and
|
|
57
|
+
* its neighbours ` `. Empty string when `line` is undefined.
|
|
58
|
+
*/
|
|
59
|
+
function excerpt(page: string, line?: number): string {
|
|
60
|
+
if (!line) return "";
|
|
61
|
+
const lines = page.split("\n");
|
|
62
|
+
const start = Math.max(1, line - 2);
|
|
63
|
+
const end = Math.min(lines.length, line + 2);
|
|
64
|
+
const out: string[] = [];
|
|
65
|
+
for (let n = start; n <= end; n++) {
|
|
66
|
+
out.push(`${n === line ? "> " : " "}${lines[n - 1]}`);
|
|
67
|
+
}
|
|
68
|
+
return out.join("\n");
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const backticked = (keys: string[]): string =>
|
|
72
|
+
keys.map((k) => `\`${k}\``).join(", ");
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Validate a rendered translation against its English `source`. Returns a
|
|
76
|
+
* model-actionable error message, or `null` when the page is publishable.
|
|
77
|
+
*/
|
|
78
|
+
export async function findTranslationError(
|
|
79
|
+
rendered: string,
|
|
80
|
+
source: string,
|
|
81
|
+
): Promise<string | null> {
|
|
82
|
+
// Validate the rendered frontmatter block FIRST, for every source shape —
|
|
83
|
+
// even when the source had none. findMdxParseError (below) blanks a leading
|
|
84
|
+
// `---` block before compiling, so a malformed block the model *added* to a
|
|
85
|
+
// frontmatter-less page would otherwise be invisible here yet still break the
|
|
86
|
+
// Mintlify deploy. Closing exactly that blind spot is this module's job, so
|
|
87
|
+
// it must hold regardless of whether the source has frontmatter.
|
|
88
|
+
const fm = findFrontmatterError(rendered);
|
|
89
|
+
if (fm) {
|
|
90
|
+
return (
|
|
91
|
+
"The YAML frontmatter (the `---` block at the top of the file) does " +
|
|
92
|
+
`not parse:\n\n${fm.message}`
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const sourceKeys = frontmatterKeys(source);
|
|
97
|
+
if (sourceKeys) {
|
|
98
|
+
// The source has frontmatter, so the translation must carry the same keys.
|
|
99
|
+
// (Its block, if present, already parsed cleanly above.)
|
|
100
|
+
const keys = frontmatterKeys(rendered);
|
|
101
|
+
if (keys === null) {
|
|
102
|
+
return (
|
|
103
|
+
"The YAML frontmatter is missing. The English source starts with a " +
|
|
104
|
+
`\`---\` block containing ${backticked(sourceKeys)}; the translation ` +
|
|
105
|
+
"must start with the same block — translate the values, keep the keys."
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
// Subset check BY DESIGN: a missing or renamed key is a content regression
|
|
109
|
+
// (the page loses its title/description), so reject it. An *extra* key is
|
|
110
|
+
// harmless — Mintlify ignores unknown frontmatter keys — so tolerate it
|
|
111
|
+
// rather than burn a retry (and risk exhausting the whole batch) on a page
|
|
112
|
+
// that would deploy fine. Reject content loss; allow harmless additions.
|
|
113
|
+
const missing = sourceKeys.filter((k) => !keys.includes(k));
|
|
114
|
+
if (missing.length > 0) {
|
|
115
|
+
return (
|
|
116
|
+
`The YAML frontmatter keys changed. Expected ${backticked(sourceKeys)} ` +
|
|
117
|
+
`but got ${backticked(keys)}. Translate the values, never rename or ` +
|
|
118
|
+
"drop a key."
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const body = await findMdxParseError(rendered);
|
|
124
|
+
if (body) {
|
|
125
|
+
const snippet = excerpt(rendered, body.line);
|
|
126
|
+
return `The MDX body does not parse: ${body.message}${
|
|
127
|
+
snippet ? `\n\n${snippet}` : ""
|
|
128
|
+
}`;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return null;
|
|
132
|
+
}
|
package/scripts/validate-mdx.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Validate that every docs
|
|
3
|
-
* runs at deploy time.
|
|
2
|
+
* Validate that every docs page (`.mdx` and `.md` — Mintlify parses both as
|
|
3
|
+
* MDX) parses with the same MDX engine Mintlify runs at deploy time.
|
|
4
4
|
*
|
|
5
|
-
* Why this exists: `mintlify validate` (the existing `docs` CI job)
|
|
6
|
-
* `docs.json` structure
|
|
7
|
-
*
|
|
8
|
-
* error (e.g. a `<slug>` that escaped its
|
|
9
|
-
* translation dropped a closing `` ` ``) passes
|
|
10
|
-
* the Mintlify deploy with:
|
|
5
|
+
* Why this exists: `mintlify validate` (the existing `docs` CI job) checks
|
|
6
|
+
* `docs.json` structure, nav-link resolution, and frontmatter YAML — but it
|
|
7
|
+
* does NOT report MDX *body* syntax errors. A page whose frontmatter is valid
|
|
8
|
+
* but whose body contains an MDX syntax error (e.g. a `<slug>` that escaped its
|
|
9
|
+
* surrounding backticks because a translation dropped a closing `` ` ``) passes
|
|
10
|
+
* `mintlify validate` but fails the Mintlify deploy with:
|
|
11
11
|
*
|
|
12
12
|
* Failed to parse page content at path tr/cli/audit.mdx:
|
|
13
13
|
* Expected a closing tag for `<slug>` (61:127-61:133) before the end of `paragraph`
|
|
@@ -21,12 +21,16 @@
|
|
|
21
21
|
*
|
|
22
22
|
* The error string above is emitted by `@mdx-js/mdx`'s micromark MDX layer,
|
|
23
23
|
* which is the same engine Mintlify uses, so compiling here reproduces the
|
|
24
|
-
* deploy-time parse faithfully.
|
|
24
|
+
* deploy-time parse faithfully. `main()` validates the frontmatter too (via
|
|
25
|
+
* `findPageError`), so this net is a strict superset of `mintlify validate`:
|
|
26
|
+
* it catches both the frontmatter YAML class that fails `mintlify validate`
|
|
27
|
+
* and the body-MDX class that `mintlify validate` lets through to deploy.
|
|
25
28
|
*/
|
|
26
29
|
import { readdirSync, statSync, readFileSync } from "node:fs";
|
|
27
30
|
import { dirname, join, relative } from "node:path";
|
|
28
31
|
import { fileURLToPath } from "node:url";
|
|
29
32
|
import { compile } from "@mdx-js/mdx";
|
|
33
|
+
import YAML from "yaml";
|
|
30
34
|
|
|
31
35
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
32
36
|
const DOCS_DIR = join(__dirname, "..", "docs");
|
|
@@ -37,21 +41,86 @@ export interface MdxParseError {
|
|
|
37
41
|
column?: number;
|
|
38
42
|
}
|
|
39
43
|
|
|
44
|
+
/**
|
|
45
|
+
* Shared frontmatter matcher. The capture group is the YAML block *body* (the
|
|
46
|
+
* text between the fences); `match[0]` is still the whole block including the
|
|
47
|
+
* fences and trailing newline, which is what `stripFrontmatter` blanks.
|
|
48
|
+
*/
|
|
49
|
+
const FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---[ \t]*\r?\n?/;
|
|
50
|
+
|
|
40
51
|
/**
|
|
41
52
|
* Replace a leading YAML frontmatter block (`--- … ---`) with blank lines.
|
|
42
53
|
*
|
|
43
54
|
* Mintlify parses frontmatter as YAML, not MDX, so it never causes an MDX parse
|
|
44
|
-
* error
|
|
45
|
-
*
|
|
55
|
+
* error — but it CAN contain a YAML syntax error, which blanking here hides
|
|
56
|
+
* from `findMdxParseError`. That gap is covered separately by
|
|
57
|
+
* `findFrontmatterError`; `findPageError` runs both. We blank rather than delete
|
|
58
|
+
* so the remaining body keeps its original line numbers — body error positions
|
|
59
|
+
* then match the real file.
|
|
46
60
|
*/
|
|
47
61
|
export function stripFrontmatter(source: string): string {
|
|
48
|
-
const match =
|
|
62
|
+
const match = FRONTMATTER_RE.exec(source);
|
|
49
63
|
if (!match) return source;
|
|
50
64
|
// Keep newlines, drop every other character, so line numbers stay aligned.
|
|
51
65
|
const blanked = match[0].replace(/[^\n]/g, "");
|
|
52
66
|
return blanked + source.slice(match[0].length);
|
|
53
67
|
}
|
|
54
68
|
|
|
69
|
+
/**
|
|
70
|
+
* Parse the leading YAML frontmatter block and return its parse error, or
|
|
71
|
+
* `null` when the block is absent (legal — some pages and every i18n README
|
|
72
|
+
* have none) or valid.
|
|
73
|
+
*
|
|
74
|
+
* This is the half of page validation that `findMdxParseError` structurally
|
|
75
|
+
* cannot do: it compiles only the *body* (frontmatter blanked), so a YAML
|
|
76
|
+
* syntax error in `title:`/`description:` — the exact class that failed the
|
|
77
|
+
* `consolidate` job's `mintlify validate` step, and that this repo's own
|
|
78
|
+
* `validate:mdx` net could not see — sailed straight through. `mintlify`
|
|
79
|
+
* parses the frontmatter as YAML, so parsing it here reproduces that check.
|
|
80
|
+
*
|
|
81
|
+
* The reported `line` is FILE-relative (the opening `---` is file line 1), so
|
|
82
|
+
* it matches the convention `findMdxParseError` already uses for body errors.
|
|
83
|
+
* That is deliberately one greater than the block-relative number `mintlify`
|
|
84
|
+
* prints — do NOT "fix" it to match mintlify; file-relative is what points a
|
|
85
|
+
* reader (or a model asked to repair the page) at the right line.
|
|
86
|
+
*/
|
|
87
|
+
export function findFrontmatterError(source: string): MdxParseError | null {
|
|
88
|
+
const match = FRONTMATTER_RE.exec(source);
|
|
89
|
+
if (!match) return null;
|
|
90
|
+
try {
|
|
91
|
+
YAML.parse(match[1]);
|
|
92
|
+
return null;
|
|
93
|
+
} catch (err) {
|
|
94
|
+
const e = err as {
|
|
95
|
+
message?: string;
|
|
96
|
+
linePos?: Array<{ line: number; col: number }>;
|
|
97
|
+
};
|
|
98
|
+
const raw = e.message ?? String(err);
|
|
99
|
+
return {
|
|
100
|
+
// Drop yaml's block-relative "at line N, column N:" phrase (it would
|
|
101
|
+
// contradict the file-relative line we report) but keep the caret-
|
|
102
|
+
// underlined excerpt after it — the single most useful signal for a
|
|
103
|
+
// model asked to repair the offending line.
|
|
104
|
+
message: raw.replace(/ at line \d+, column \d+:/, ":"),
|
|
105
|
+
line: e.linePos?.[0] ? e.linePos[0].line + 1 : undefined,
|
|
106
|
+
column: e.linePos?.[0]?.col,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Validate a full page the way the Mintlify deploy does: frontmatter YAML
|
|
113
|
+
* first, then the MDX body. Returns the first error found, or `null`. This is
|
|
114
|
+
* the single entry point shared by `validate:mdx` (`main()` below) and the
|
|
115
|
+
* translation pipeline's generation-time gate, so the two can never disagree
|
|
116
|
+
* about what is publishable.
|
|
117
|
+
*/
|
|
118
|
+
export async function findPageError(
|
|
119
|
+
source: string,
|
|
120
|
+
): Promise<MdxParseError | null> {
|
|
121
|
+
return findFrontmatterError(source) ?? (await findMdxParseError(source));
|
|
122
|
+
}
|
|
123
|
+
|
|
55
124
|
/**
|
|
56
125
|
* Compile one MDX source string with the deploy-time parser. Returns `null`
|
|
57
126
|
* when it parses cleanly, or the parse error (with position) otherwise.
|
|
@@ -91,12 +160,20 @@ export function encodeAnnotation(value: string): string {
|
|
|
91
160
|
.replace(/\n/g, "%0A");
|
|
92
161
|
}
|
|
93
162
|
|
|
94
|
-
|
|
163
|
+
/**
|
|
164
|
+
* Collect every Mintlify content page under `dir`. Mintlify runs BOTH `.mdx`
|
|
165
|
+
* and `.md` files through the same MDX pipeline at deploy time, so a `.md` page
|
|
166
|
+
* with an MDX syntax error (e.g. an HTML `<!-- -->` comment, which MDX rejects)
|
|
167
|
+
* fails the deploy exactly like a broken `.mdx` would. The docs/i18n README
|
|
168
|
+
* translations are `.md`, so restricting this walk to `.mdx` let their breakage
|
|
169
|
+
* sail past this safety net and reach the post-merge deploy — collect both.
|
|
170
|
+
*/
|
|
171
|
+
export function collectMdxFiles(dir: string): string[] {
|
|
95
172
|
const out: string[] = [];
|
|
96
173
|
for (const entry of readdirSync(dir)) {
|
|
97
174
|
const full = join(dir, entry);
|
|
98
175
|
if (statSync(full).isDirectory()) out.push(...collectMdxFiles(full));
|
|
99
|
-
else if (entry.endsWith(".mdx")) out.push(full);
|
|
176
|
+
else if (entry.endsWith(".mdx") || entry.endsWith(".md")) out.push(full);
|
|
100
177
|
}
|
|
101
178
|
return out;
|
|
102
179
|
}
|
|
@@ -106,7 +183,7 @@ async function main(): Promise<void> {
|
|
|
106
183
|
const failures: Array<{ file: string; error: MdxParseError }> = [];
|
|
107
184
|
|
|
108
185
|
for (const file of files) {
|
|
109
|
-
const error = await
|
|
186
|
+
const error = await findPageError(readFileSync(file, "utf-8"));
|
|
110
187
|
if (error) failures.push({ file: relative(process.cwd(), file), error });
|
|
111
188
|
}
|
|
112
189
|
|
package/src/audit/cli.ts
CHANGED
|
@@ -22,6 +22,7 @@ import { trackHookEvent } from "../hooks/hook-telemetry";
|
|
|
22
22
|
import { getInstanceId } from "../../lib/telemetry-id";
|
|
23
23
|
import { sanitizeErrorMessage } from "../../lib/telemetry-sanitize";
|
|
24
24
|
import { openWhenReady } from "./open-browser";
|
|
25
|
+
import { brandAnsi, ANSI_RESET, ANSI_BOLD, ANSI_DIM } from "../hooks/tui";
|
|
25
26
|
|
|
26
27
|
/** Port the bundled dashboard binds to. Matches `scripts/launch.ts`'s default
|
|
27
28
|
* for `start` mode, which `failproofai` (bare) already uses. */
|
|
@@ -59,12 +60,17 @@ WHAT IT DOES
|
|
|
59
60
|
`.trimStart();
|
|
60
61
|
|
|
61
62
|
// ── ANSI helpers ────────────────────────────────────────────────────────────
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
const
|
|
63
|
+
// Colours come from the shared brand palette in hooks/tui.ts, so `audit` reads
|
|
64
|
+
// as the same product as `config`. This file used to define its own 256-colour
|
|
65
|
+
// set (a green and a blue that appear nowhere in the brand). The palette has
|
|
66
|
+
// two accents rather than three, so success and command/URL text share the
|
|
67
|
+
// teal — the glyph (✓) and the surrounding copy already distinguish them.
|
|
68
|
+
const RESET = ANSI_RESET;
|
|
69
|
+
const DIM = ANSI_DIM;
|
|
70
|
+
const BOLD = ANSI_BOLD;
|
|
71
|
+
const PINK = brandAnsi("pink");
|
|
72
|
+
const GREEN = brandAnsi("guide"); // success — brand teal
|
|
73
|
+
const CYAN = brandAnsi("guide"); // commands & URLs — brand teal
|
|
68
74
|
const SPINNER = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
69
75
|
|
|
70
76
|
function colorOn(): boolean {
|
|
@@ -237,6 +243,26 @@ function printSummary(result: AuditResult): void {
|
|
|
237
243
|
for (const line of buildSummary(result)) process.stdout.write(` ${line}\n`);
|
|
238
244
|
}
|
|
239
245
|
|
|
246
|
+
// ── Audit telemetry ──────────────────────────────────────────────────────────
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Which entry point ran the audit. `onboarding` is the automatic post-setup run;
|
|
250
|
+
* `cli` is an explicit `failproofai audit`. Carried on every cli_audit_* event so
|
|
251
|
+
* the first audit a user ever runs is distinguishable from later deliberate ones.
|
|
252
|
+
*/
|
|
253
|
+
type AuditSource = "cli" | "onboarding";
|
|
254
|
+
|
|
255
|
+
/** Shared so both entry points report cli_audit_completed identically. */
|
|
256
|
+
function auditCompletedProps(source: AuditSource, result: AuditResult) {
|
|
257
|
+
return {
|
|
258
|
+
source,
|
|
259
|
+
events_scanned: result.eventsScanned,
|
|
260
|
+
sessions_scanned: result.transcripts.scanned,
|
|
261
|
+
total_hits: result.totals.hits,
|
|
262
|
+
findings: result.results.length,
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
|
|
240
266
|
// ── Post-setup background audit ────────────────────────────────────────────────
|
|
241
267
|
|
|
242
268
|
/**
|
|
@@ -252,7 +278,13 @@ function printSummary(result: AuditResult): void {
|
|
|
252
278
|
*/
|
|
253
279
|
export async function runPostSetupAudit(): Promise<void> {
|
|
254
280
|
if (process.env.FAILPROOFAI_NO_AUTO_AUDIT === "1") return;
|
|
255
|
-
|
|
281
|
+
|
|
282
|
+
const instanceId = getInstanceId();
|
|
283
|
+
// Fire-and-forget, as in runAuditCli: the multi-second scan below keeps the
|
|
284
|
+
// process alive long enough for this to land, and the completed/failed event
|
|
285
|
+
// that follows is awaited.
|
|
286
|
+
void trackHookEvent(instanceId, "cli_audit_started", { source: "onboarding" });
|
|
287
|
+
|
|
256
288
|
process.stdout.write(
|
|
257
289
|
`\n ${c(PINK, "✦")} ${c(BOLD, "failproofai audit now running")} ${c(DIM, "· ctrl+c to stop")}\n\n`,
|
|
258
290
|
);
|
|
@@ -260,13 +292,25 @@ export async function runPostSetupAudit(): Promise<void> {
|
|
|
260
292
|
let result: AuditResult;
|
|
261
293
|
try {
|
|
262
294
|
result = await runWithProgress({});
|
|
263
|
-
} catch {
|
|
295
|
+
} catch (err) {
|
|
296
|
+
// Awaited: this function returns straight into the dashboard boot, and a
|
|
297
|
+
// fire-and-forget fetch would race it.
|
|
298
|
+
await trackHookEvent(instanceId, "cli_audit_failed", {
|
|
299
|
+
source: "onboarding",
|
|
300
|
+
error_type: err instanceof Error ? err.name : "unknown",
|
|
301
|
+
error_message: sanitizeErrorMessage(err),
|
|
302
|
+
});
|
|
264
303
|
process.stdout.write(
|
|
265
304
|
` ${c(PINK, "!")} ${c(DIM, "audit couldn't finish — run")} ${c(CYAN, "failproofai audit")} ${c(DIM, "later.")}\n\n`,
|
|
266
305
|
);
|
|
267
306
|
return;
|
|
268
307
|
}
|
|
269
308
|
|
|
309
|
+
// Reported before the empty-history return below, so an onboarding audit that
|
|
310
|
+
// finds nothing is still counted — matching runAuditCli, which reports
|
|
311
|
+
// completed regardless of what the scan turned up.
|
|
312
|
+
await trackHookEvent(instanceId, "cli_audit_completed", auditCompletedProps("onboarding", result));
|
|
313
|
+
|
|
270
314
|
if (result.eventsScanned === 0) {
|
|
271
315
|
process.stdout.write(
|
|
272
316
|
`\n ${c(DIM, "no agent sessions to audit yet — come back after using your agent.")}\n\n`,
|
|
@@ -328,13 +372,7 @@ export async function runAuditCli(args: string[]): Promise<void> {
|
|
|
328
372
|
// would otherwise drop this event. On the dashboard path launch() keeps the
|
|
329
373
|
// process alive, but awaiting makes delivery reliable on every exit path.
|
|
330
374
|
// Bounded (5s) and never throws.
|
|
331
|
-
await trackHookEvent(instanceId, "cli_audit_completed",
|
|
332
|
-
source: "cli",
|
|
333
|
-
events_scanned: result.eventsScanned,
|
|
334
|
-
sessions_scanned: result.transcripts.scanned,
|
|
335
|
-
total_hits: result.totals.hits,
|
|
336
|
-
findings: result.results.length,
|
|
337
|
-
});
|
|
375
|
+
await trackHookEvent(instanceId, "cli_audit_completed", auditCompletedProps("cli", result));
|
|
338
376
|
|
|
339
377
|
// No sessions on disk — guide the user instead of opening an empty dashboard.
|
|
340
378
|
if (result.eventsScanned === 0) {
|
package/src/auth/cli.ts
CHANGED
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
writeAuth,
|
|
29
29
|
} from "../../lib/auth/auth-store";
|
|
30
30
|
import { CliError } from "../cli-error";
|
|
31
|
+
import { brandAnsi, colorsEnabled, ANSI_RESET, ANSI_DIM } from "../hooks/tui";
|
|
31
32
|
import { trackHookEvent } from "../hooks/hook-telemetry";
|
|
32
33
|
import { getInstanceId } from "../../lib/telemetry-id";
|
|
33
34
|
|
|
@@ -123,11 +124,19 @@ function prompt(question: string, opts: { hidden?: boolean } = {}): Promise<stri
|
|
|
123
124
|
});
|
|
124
125
|
}
|
|
125
126
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
127
|
+
// Brand palette (see hooks/tui.ts) rather than this file's former private
|
|
128
|
+
// 256-colour set, so `auth` matches `config` and `audit`. Resolved once here
|
|
129
|
+
// and blanked when colour is off — these are interpolated directly into
|
|
130
|
+
// template literals at ~15 call sites, which previously meant `auth` emitted
|
|
131
|
+
// escape codes even under NO_COLOR. Blanking the constants fixes every call
|
|
132
|
+
// site at once. The palette has no red, and both uses are recoverable input
|
|
133
|
+
// errors ("try again") rather than failures, so they take the amber warn hue.
|
|
134
|
+
const COLOR = colorsEnabled(process.stdout);
|
|
135
|
+
const DIM = COLOR ? ANSI_DIM : "";
|
|
136
|
+
const RESET = COLOR ? ANSI_RESET : "";
|
|
137
|
+
const PINK = COLOR ? brandAnsi("pink") : "";
|
|
138
|
+
const GREEN = COLOR ? brandAnsi("guide") : "";
|
|
139
|
+
const RED = COLOR ? brandAnsi("warn") : "";
|
|
131
140
|
|
|
132
141
|
function emailLooksValid(email: string): boolean {
|
|
133
142
|
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
|