pm4ai 0.0.73

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.

Potentially problematic release.


This version of pm4ai might be problematic. Click here for more details.

Files changed (51) hide show
  1. package/README.md +34 -0
  2. package/dist/audit-oQQfgtxr.mjs +287 -0
  3. package/dist/cleanup-M-ALxTqh.mjs +35 -0
  4. package/dist/cli.d.mts +1 -0
  5. package/dist/cli.mjs +77 -0
  6. package/dist/dashboard-DVRNZGun.mjs +39 -0
  7. package/dist/discover-d8ENQC1K.mjs +172 -0
  8. package/dist/fix-BcMN_cuG.mjs +260 -0
  9. package/dist/fix-DvtItv_V.mjs +2 -0
  10. package/dist/guide-BS7-RqpH.d.mts +4 -0
  11. package/dist/guide-CifUmtQN.mjs +59 -0
  12. package/dist/guide.d.mts +2 -0
  13. package/dist/guide.mjs +2 -0
  14. package/dist/ignores-BBl55eUM.mjs +37 -0
  15. package/dist/index.d.mts +83 -0
  16. package/dist/index.mjs +11 -0
  17. package/dist/init-C-073mRX.mjs +120 -0
  18. package/dist/list-QdJPgkEO.mjs +31 -0
  19. package/dist/package-NpIViQjo.mjs +4 -0
  20. package/dist/schemas-Dsbtf6P2.mjs +51 -0
  21. package/dist/schemas.d.mts +48 -0
  22. package/dist/schemas.mjs +2 -0
  23. package/dist/setup-BPuE4oWT.mjs +164 -0
  24. package/dist/status-ByiuW1iF.mjs +2 -0
  25. package/dist/status-CzCNkG58.mjs +1775 -0
  26. package/dist/sync-DN1rgN3P.mjs +732 -0
  27. package/dist/templates/cli/package.json +30 -0
  28. package/dist/templates/cli/src/cli.ts +16 -0
  29. package/dist/templates/cli/src/index.ts +2 -0
  30. package/dist/templates/cli/src/tui.tsx +57 -0
  31. package/dist/templates/cli/tsdown.config.ts +9 -0
  32. package/dist/templates/docs/content/docs/index.mdx +6 -0
  33. package/dist/templates/docs/package.json +20 -0
  34. package/dist/templates/docs/source.config.ts +16 -0
  35. package/dist/templates/docs/src/app/(home)/page.tsx +11 -0
  36. package/dist/templates/lib/package.json +22 -0
  37. package/dist/templates/lib/src/index.ts +2 -0
  38. package/dist/templates/lib/tsdown.config.ts +9 -0
  39. package/dist/templates/root-package.txt +38 -0
  40. package/dist/templates/web/package.json +17 -0
  41. package/dist/templates/web/src/app/page.tsx +6 -0
  42. package/dist/templates/web/src/app/providers.tsx +15 -0
  43. package/dist/utils-CpkOMuQN.mjs +221 -0
  44. package/dist/watch-D4OSFClu.mjs +566 -0
  45. package/dist/watch-emitter-uTmZ3oQd.mjs +177 -0
  46. package/dist/watch-state-DIMHiLr5.d.mts +118 -0
  47. package/dist/watch-state-wF-NfC-k.mjs +274 -0
  48. package/dist/watch-state.d.mts +2 -0
  49. package/dist/watch-state.mjs +2 -0
  50. package/dist/watch-types-BzSNCGb2.mjs +22 -0
  51. package/package.json +65 -0
@@ -0,0 +1,1775 @@
1
+ import { B as UI_PACKAGE_NAME, D as FORBIDDEN_LOCKFILES, E as EXPECTED, F as RG_EXCLUDE, I as SWIFTBAR_FONT, T as DEFAULT_SCRIPTS, a as getBunVersion, b as CONDITIONAL_MUST_EXIST_FILES, f as projectName, g as resolveManagedFiles, h as rel, i as detectCapabilities, j as MUST_EXIST_FILES, l as isExtended, m as readPkg, o as getGhRepo, p as readJson, r as debug, s as getTsconfigTypes, u as isInsideProject } from "./utils-CpkOMuQN.mjs";
2
+ import { t as audit } from "./audit-oQQfgtxr.mjs";
3
+ import { n as emitToSocket, s as spawnBackgroundCheck } from "./watch-emitter-uTmZ3oQd.mjs";
4
+ import { n as discoverSources, t as discover } from "./discover-d8ENQC1K.mjs";
5
+ import { r as createEvent } from "./watch-types-BzSNCGb2.mjs";
6
+ import { $, Glob, file } from "bun";
7
+ import { existsSync } from "node:fs";
8
+ import { join } from "node:path";
9
+ //#region src/banned.ts
10
+ const b = (...names) => `import { ${names.join(", ")} } from bun`;
11
+ const n = (mod, detail) => detail ? `node:${mod} ${detail}` : `node:${mod}`;
12
+ const w = (api, detail) => detail ? `${api} ${detail}` : api;
13
+ const BANNED = {
14
+ animation: { motion: [
15
+ "\"@formkit/auto-animate\"",
16
+ "\"anime\"",
17
+ "\"gsap\"",
18
+ "\"react-flip-move\"",
19
+ "\"react-spring\"",
20
+ "\"react-transition-group\""
21
+ ] },
22
+ api: {
23
+ "bun WebSocket": ["\"socket.io-client\""],
24
+ "bun WebSocket or Unix socket": ["\"socket.io\""],
25
+ oRPC: [
26
+ "\"@apollo/server\"",
27
+ "\"@trpc",
28
+ "\"graphql\"",
29
+ "\"graphql-codegen\"",
30
+ "\"json-schema-to-typescript\""
31
+ ],
32
+ "oRPC or tanstack-query": ["\"@apollo/client\"", "\"urql\""]
33
+ },
34
+ auth: {
35
+ "better-auth": [
36
+ "\"jose\"",
37
+ "\"jsonwebtoken\"",
38
+ "\"lucia\"",
39
+ "\"next-auth\"",
40
+ "\"passport\""
41
+ ],
42
+ "drizzle + better-auth": [
43
+ "\"@clerk",
44
+ "\"@firebase",
45
+ "\"@supabase",
46
+ "\"firebase\""
47
+ ]
48
+ },
49
+ backend: {
50
+ "elysia or next.js": [
51
+ "\"@nestjs",
52
+ "\"adonis\"",
53
+ "\"feathers\""
54
+ ],
55
+ "elysia or next.js api routes": [
56
+ "\"express\"",
57
+ "\"fastify\"",
58
+ "\"hono\"",
59
+ "\"koa\""
60
+ ]
61
+ },
62
+ build: {
63
+ "bun built-in transpiler or tsdown": [
64
+ "\"@babel",
65
+ "\"@swc",
66
+ "\"babel-cli\"",
67
+ "\"babel-core\"",
68
+ "\"babel-plugin-transform",
69
+ "\"babel-preset-env\"",
70
+ "\"babel-register\""
71
+ ],
72
+ "not needed": [
73
+ "\"bower\"",
74
+ "\"broccoli\"",
75
+ "\"grunt\"",
76
+ "\"gulp\""
77
+ ],
78
+ tsdown: [
79
+ "\"bunchee\"",
80
+ "\"esbuild\"",
81
+ "\"ncc\"",
82
+ "\"pkgroll\"",
83
+ "\"rollup\"",
84
+ "\"sucrase\"",
85
+ "\"terser\"",
86
+ "\"tsc-alias\"",
87
+ "\"tslib\"",
88
+ "\"tsup\"",
89
+ "\"uglify-js\"",
90
+ "\"unbuild\"",
91
+ "\"unplugin\"",
92
+ "\"vite-plugin-dts\""
93
+ ],
94
+ "tsdown/turbopack": [
95
+ "\"babel-loader\"",
96
+ "\"browserify\"",
97
+ "\"parcel\"",
98
+ "\"swc-loader\"",
99
+ "\"ts-loader\"",
100
+ "\"webpack\""
101
+ ]
102
+ },
103
+ carousel: { "shadcn carousel": [
104
+ "\"keen-slider\"",
105
+ "\"react-slick\"",
106
+ "\"slick-carousel\"",
107
+ "\"splide\"",
108
+ "\"swiper\""
109
+ ] },
110
+ charts: { "recharts (via shadcn)": [
111
+ "\"@nivo",
112
+ "\"chart.js\"",
113
+ "\"d3\"",
114
+ "\"tremor\"",
115
+ "\"victory\""
116
+ ] },
117
+ cli: {
118
+ "bun process.argv": [
119
+ "\"arg\"",
120
+ "\"cac\"",
121
+ "\"citty\"",
122
+ "\"commander\"",
123
+ "\"meow\"",
124
+ "\"minimist\"",
125
+ "\"nopt\"",
126
+ "\"yargs\""
127
+ ],
128
+ ink: [
129
+ "\"blessed\"",
130
+ "\"cli-table\"",
131
+ "\"cli-table3\"",
132
+ "\"cli-truncate\"",
133
+ "\"cliui\"",
134
+ "\"clipanion\"",
135
+ "\"enquirer\"",
136
+ "\"figures\"",
137
+ "\"inquirer\"",
138
+ "\"listr2\"",
139
+ "\"log-symbols\"",
140
+ "\"log-update\"",
141
+ "\"oclif\"",
142
+ "\"cli-progress\"",
143
+ "\"prompts\"",
144
+ "\"terminal-kit\"",
145
+ "\"terminal-link\""
146
+ ],
147
+ "ink Box": ["\"boxen\""],
148
+ [`ink or ${b("$")}`]: ["\"clipboardy\""],
149
+ [`ink or ${b("color")}`]: [
150
+ "\"ansi-colors\"",
151
+ "\"ansi-styles\"",
152
+ "\"chalk\"",
153
+ "\"chalk-template\"",
154
+ "\"colorette\"",
155
+ "\"kleur\"",
156
+ "\"picocolors\"",
157
+ "\"supports-color\"",
158
+ "\"yoctocolors\""
159
+ ],
160
+ "ink-spinner": [
161
+ "\"cli-spinners\"",
162
+ "\"ora\"",
163
+ "\"progress\""
164
+ ]
165
+ },
166
+ codeGen: { "bun scripts": [
167
+ "\"hygen\"",
168
+ "\"plop\"",
169
+ "\"yeoman-generator\""
170
+ ] },
171
+ compat: {
172
+ "async/await": ["\"co\"", "\"fibers\""],
173
+ [w("AbortController")]: ["\"abortcontroller-polyfill\""],
174
+ [w("Blob")]: ["\"cross-blob\"", "\"fetch-blob\""],
175
+ "native ES2024+ in bun": [
176
+ "\"babel-polyfill\"",
177
+ "\"core-js\"",
178
+ "\"es5-shim\"",
179
+ "\"es6-promise\"",
180
+ "\"es6-shim\"",
181
+ "\"left-pad\"",
182
+ "\"object-assign\"",
183
+ "\"regenerator-runtime\"",
184
+ "\"setimmediate\""
185
+ ],
186
+ [w("FormData")]: ["\"form-data\"", "\"formdata-node\""],
187
+ [w("Promise")]: [
188
+ "\"any-promise\"",
189
+ "\"bluebird\"",
190
+ "\"lie\"",
191
+ "\"pinkie\"",
192
+ "\"q\"",
193
+ "\"rsvp\""
194
+ ],
195
+ [w("ReadableStream")]: ["\"web-streams-polyfill\""],
196
+ [w("URL")]: ["\"whatwg-url\""],
197
+ [w("fetch")]: ["\"whatwg-fetch\""],
198
+ [n("path")]: ["\"path-browserify\""],
199
+ [n("stream")]: ["\"readable-stream\"", "\"stream-browserify\""],
200
+ [`${n("util")} promisify or ${n("fs/promises")}`]: [
201
+ "\"mz\"",
202
+ "\"pify\"",
203
+ "\"thenify\""
204
+ ]
205
+ },
206
+ components: {
207
+ "cnsync (readonly/ui)": ["\"@radix-ui"],
208
+ "shadcn + cnsync": [
209
+ "\"@ariakit/react\"",
210
+ "\"@headlessui",
211
+ "\"@react-aria",
212
+ "\"rc-checkbox\"",
213
+ "\"rc-collapse\"",
214
+ "\"rc-dialog\"",
215
+ "\"rc-dropdown\"",
216
+ "\"rc-field-form\"",
217
+ "\"rc-form\"",
218
+ "\"rc-input-number\"",
219
+ "\"rc-menu\"",
220
+ "\"rc-notification\"",
221
+ "\"rc-pagination\"",
222
+ "\"rc-picker\"",
223
+ "\"rc-progress\"",
224
+ "\"rc-select\"",
225
+ "\"rc-slider\"",
226
+ "\"rc-switch\"",
227
+ "\"rc-tabs\"",
228
+ "\"rc-tooltip\"",
229
+ "\"rc-upload\"",
230
+ "\"react-aria\""
231
+ ],
232
+ "shadcn + tailwind": [
233
+ "\"@blueprintjs/core\"",
234
+ "\"@chakra-ui",
235
+ "\"@mantine",
236
+ "\"@mui",
237
+ "\"@nextui-org/react\"",
238
+ "\"antd\"",
239
+ "\"daisyui\"",
240
+ "\"evergreen-ui\"",
241
+ "\"flowbite\"",
242
+ "\"grommet\"",
243
+ "\"primereact\"",
244
+ "\"react-bootstrap\"",
245
+ "\"reactstrap\"",
246
+ "\"rsuite\"",
247
+ "\"semantic-ui-react\""
248
+ ]
249
+ },
250
+ compression: {
251
+ [b("Archive")]: [
252
+ "\"archiver\"",
253
+ "\"tar\"",
254
+ "\"tar-fs\"",
255
+ "\"tar-stream\""
256
+ ],
257
+ [b("gzipSync", "deflateSync")]: [
258
+ "\"adm-zip\"",
259
+ "\"fflate\"",
260
+ "\"jszip\"",
261
+ "\"lz-string\"",
262
+ "\"pako\""
263
+ ]
264
+ },
265
+ config: {
266
+ "bun .env auto-loading": ["\"convict\""],
267
+ [b("file")]: [
268
+ "\"conf\"",
269
+ "\"configstore\"",
270
+ "\"cosmiconfig\""
271
+ ],
272
+ "zod + bun .env auto-loading": ["\"@t3-oss/env", "\"envalid\""]
273
+ },
274
+ crypto: {
275
+ "crypto.randomUUID()": [
276
+ "\"cuid\"",
277
+ "\"@paralleldrive/cuid2\"",
278
+ "\"flake-idgen\"",
279
+ "\"hyperid\"",
280
+ "\"ksuid\"",
281
+ "\"nanoid\"",
282
+ "\"short-uuid\"",
283
+ "\"ulid\"",
284
+ "\"uuid\"",
285
+ "\"xid\""
286
+ ],
287
+ [b("password")]: [
288
+ "\"argon2\"",
289
+ "\"bcrypt\"",
290
+ "\"bcryptjs\"",
291
+ "\"scrypt-js\""
292
+ ],
293
+ [w("SubtleCrypto")]: [
294
+ "\"crypto-js\"",
295
+ "\"node-forge\"",
296
+ "\"node-rsa\"",
297
+ "\"tweetnacl\""
298
+ ]
299
+ },
300
+ date: { "date-fns": [
301
+ "\"dayjs\"",
302
+ "\"luxon\"",
303
+ "\"moment\"",
304
+ "\"moment-timezone\""
305
+ ] },
306
+ dbDrivers: {
307
+ drizzle: ["\"mongodb\""],
308
+ [`drizzle or ${b("sql")}`]: [
309
+ "\"@neondatabase",
310
+ "\"@planetscale/database\"",
311
+ "\"@vercel/postgres\"",
312
+ "\"libsql\"",
313
+ "\"mysql2\"",
314
+ "\"pg\"",
315
+ "\"postgres\""
316
+ ]
317
+ },
318
+ dnd: { "@dnd-kit": [
319
+ "\"react-beautiful-dnd\"",
320
+ "\"react-dnd\"",
321
+ "\"react-draggable\"",
322
+ "\"react-sortable-hoc\""
323
+ ] },
324
+ e2e: {
325
+ "bun:test + playwright": ["\"enzyme\"", "\"react-test-renderer\""],
326
+ playwright: [
327
+ "\"cheerio\"",
328
+ "\"cypress\"",
329
+ "\"nightwatch\"",
330
+ "\"puppeteer\"",
331
+ "\"selenium\"",
332
+ "\"webdriver\""
333
+ ]
334
+ },
335
+ email: { resend: [
336
+ "\"@sendgrid/mail\"",
337
+ "\"mailgun.js\"",
338
+ "\"postmark\""
339
+ ] },
340
+ encoding: {
341
+ [b("escapeHTML")]: [
342
+ "\"entities\"",
343
+ "\"escape-html\"",
344
+ "\"he\""
345
+ ],
346
+ [`${w("Response")} headers or elysia`]: [
347
+ "\"content-type\"",
348
+ "\"mime\"",
349
+ "\"mime-types\""
350
+ ],
351
+ [w("TextDecoder")]: ["\"iconv-lite\""],
352
+ [w("URL")]: ["\"url\"", "\"url-parse\""],
353
+ [w("URLSearchParams")]: [
354
+ "\"qs\"",
355
+ "\"query-string\"",
356
+ "\"querystring\""
357
+ ],
358
+ [`${w("atob")}/${w("btoa")}`]: ["\"base64-js\"", "\"js-base64\""]
359
+ },
360
+ errorTracking: { "platform-managed or error boundary": [
361
+ "\"@bugsnag/js\"",
362
+ "\"@honeybadger-io/js\"",
363
+ "\"rollbar\""
364
+ ] },
365
+ expressMw: { "not needed with elysia/next.js": [
366
+ "\"body-parser\"",
367
+ "\"busboy\"",
368
+ "\"cookie-parser\"",
369
+ "\"cors\"",
370
+ "\"express-rate-limit\"",
371
+ "\"helmet\"",
372
+ "\"morgan\"",
373
+ "\"multer\""
374
+ ] },
375
+ files: {
376
+ [b("Glob")]: [
377
+ "\"@nodelib/fs.walk\"",
378
+ "\"fast-glob\"",
379
+ "\"glob\"",
380
+ "\"klaw\"",
381
+ "\"micromatch\"",
382
+ "\"minimatch\"",
383
+ "\"globby\"",
384
+ "\"tinyglobby\"",
385
+ "\"is-extglob\"",
386
+ "\"is-glob\"",
387
+ "\"readdirp\"",
388
+ "\"recursive-readdir\"",
389
+ "\"walk\""
390
+ ],
391
+ [`${b("Glob")} or ${n("fs")} walk`]: ["\"find-up\"", "\"pkg-dir\""],
392
+ "import.meta.resolve": ["\"resolve\"", "\"resolve-from\""],
393
+ [n("fs")]: [
394
+ "\"cpy\"",
395
+ "\"fs-extra\"",
396
+ "\"graceful-fs\"",
397
+ "\"load-json-file\"",
398
+ "\"ncp\"",
399
+ "\"proper-lockfile\"",
400
+ "\"write-file-atomic\"",
401
+ "\"write-json-file\""
402
+ ],
403
+ [n("fs", "mkdir recursive")]: ["\"make-dir\"", "\"mkdirp\""],
404
+ [`${n("fs")} or ${b("$")}`]: ["\"del\"", "\"rimraf\""],
405
+ [`${n("os")} tmpdir + ${n("fs")}`]: ["\"tmp\"", "\"tmp-promise\""],
406
+ [n("path")]: ["\"normalize-path\""],
407
+ [n("path/posix")]: ["\"slash\""],
408
+ [b("readableStreamToText", "readableStreamToBytes")]: ["\"concat-stream\"", "\"get-stream\""]
409
+ },
410
+ forms: { "tanstack-form": [
411
+ "\"@hookform/resolvers\"",
412
+ "\"final-form\"",
413
+ "\"formik\"",
414
+ "\"informed\"",
415
+ "\"react-final-form\"",
416
+ "\"react-hook-form\""
417
+ ] },
418
+ frontend: {
419
+ react: [
420
+ "\"@angular",
421
+ "\"alpinejs\"",
422
+ "\"backbone\"",
423
+ "\"coffeescript\"",
424
+ "\"inferno\"",
425
+ "\"lit\"",
426
+ "\"mithril\"",
427
+ "\"preact\"",
428
+ "\"solid-js\"",
429
+ "\"stimulus\"",
430
+ "\"svelte\"",
431
+ "\"vue\""
432
+ ],
433
+ "react + next.js": ["\"qwik\""]
434
+ },
435
+ hashing: { [b("CryptoHasher")]: [
436
+ "\"crc\"",
437
+ "\"crc-32\"",
438
+ "\"hash-sum\"",
439
+ "\"hash-wasm\"",
440
+ "\"js-sha256\"",
441
+ "\"js-sha3\"",
442
+ "\"md5\"",
443
+ "\"md5-file\"",
444
+ "\"object-hash\"",
445
+ "\"sha.js\"",
446
+ "\"spark-md5\"",
447
+ "\"xxhash-wasm\"",
448
+ "\"xxhashjs\""
449
+ ] },
450
+ http: {
451
+ [w("fetch")]: ["\"undici\""],
452
+ [`${w("fetch")} or ky`]: [
453
+ "\"axios\"",
454
+ "\"bent\"",
455
+ "\"cross-fetch\"",
456
+ "\"got\"",
457
+ "\"isomorphic-fetch\"",
458
+ "\"node-fetch\"",
459
+ "\"ofetch\"",
460
+ "\"needle\"",
461
+ "\"request\"",
462
+ "\"request-promise\"",
463
+ "\"superagent\"",
464
+ "\"wretch\""
465
+ ]
466
+ },
467
+ i18n: {
468
+ "next.js built-in i18n": [
469
+ "\"next-intl\"",
470
+ "\"react-i18next\"",
471
+ "\"react-intl\""
472
+ ],
473
+ [`next.js built-in i18n or ${w("Intl")}`]: ["\"i18next\""]
474
+ },
475
+ icons: { lucide: [
476
+ "\"@fortawesome",
477
+ "\"@heroicons",
478
+ "\"@iconify",
479
+ "\"@tabler/icons-react\"",
480
+ "\"font-awesome\"",
481
+ "\"phosphor-react\"",
482
+ "\"react-icons\""
483
+ ] },
484
+ infra: {
485
+ [`${w("Map")} or bun:sqlite`]: [
486
+ "\"keyv\"",
487
+ "\"lru-cache\"",
488
+ "\"quick-lru\""
489
+ ],
490
+ [`better-auth or ${b("Cookie")}`]: [
491
+ "\"connect-redis\"",
492
+ "\"cookie\"",
493
+ "\"express-session\""
494
+ ],
495
+ "bun:sqlite": ["\"better-sqlite3\""],
496
+ "elysia plugin or platform": ["\"@upstash/ratelimit\"", "\"rate-limiter-flexible\""],
497
+ [`${b("cron")} or platform cron`]: [
498
+ "\"cron\"",
499
+ "\"cron-parser\"",
500
+ "\"cronstrue\"",
501
+ "\"node-cron\""
502
+ ],
503
+ [`${b("redis")} or upstash REST`]: [
504
+ "\"@upstash/redis\"",
505
+ "\"ioredis\"",
506
+ "\"redis\""
507
+ ],
508
+ [b("s3", "S3Client")]: ["\"@aws-sdk", "\"uploadthing\""],
509
+ [`${b("serve")} (WebSocket)`]: ["\"ws\""],
510
+ [`${b("sql")} or platform queues`]: ["\"bull\"", "\"bullmq\""],
511
+ "next.js rewrites or elysia": ["\"http-proxy\"", "\"http-proxy-middleware\""],
512
+ "next/image optimization": [
513
+ "\"imagemin\"",
514
+ "\"jimp\"",
515
+ "\"sharp\""
516
+ ],
517
+ "next/image placeholder=blur": ["\"blurhash\"", "\"plaiceholder\""],
518
+ "not needed with elysia/next.js": [
519
+ "\"compression\"",
520
+ "\"connect\"",
521
+ "\"finalhandler\"",
522
+ "\"formidable\"",
523
+ "\"http-errors\"",
524
+ "\"on-finished\"",
525
+ "\"raw-body\"",
526
+ "\"serve-favicon\"",
527
+ "\"serve-static\""
528
+ ],
529
+ "platform-managed or error boundary": ["\"@sentry"],
530
+ resend: ["\"nodemailer\""]
531
+ },
532
+ intl: {
533
+ [`${w("Intl")}.NumberFormat`]: [
534
+ "\"accounting\"",
535
+ "\"bytes\"",
536
+ "\"filesize\"",
537
+ "\"numeral\"",
538
+ "\"pretty-bytes\""
539
+ ],
540
+ [`${w("Intl")}.PluralRules`]: ["\"pluralize\""],
541
+ [`${w("Intl")}.RelativeTimeFormat`]: [
542
+ "\"humanize-duration\"",
543
+ "\"ms\"",
544
+ "\"timeago.js\""
545
+ ]
546
+ },
547
+ logging: {
548
+ lintmax: ["\"flow-bin\"", "\"tslint\""],
549
+ "logtape or consola": [
550
+ "\"bunyan\"",
551
+ "\"debug\"",
552
+ "\"log4js\"",
553
+ "\"loglevel\"",
554
+ "\"npmlog\"",
555
+ "\"pino\"",
556
+ "\"roarr\"",
557
+ "\"signale\"",
558
+ "\"tslog\"",
559
+ "\"winston\""
560
+ ]
561
+ },
562
+ markdown: { [`${b("markdown")} or remark/rehype`]: [
563
+ "\"gray-matter\"",
564
+ "\"markdown-it\"",
565
+ "\"marked\"",
566
+ "\"micromark\"",
567
+ "\"react-markdown\"",
568
+ "\"showdown\"",
569
+ "\"turndown\""
570
+ ] },
571
+ metaFramework: {
572
+ "next.js": [
573
+ "\"@sveltejs/kit\"",
574
+ "\"astro\"",
575
+ "\"blitz\"",
576
+ "\"gatsby\"",
577
+ "\"nuxt\"",
578
+ "\"redwood\"",
579
+ "\"remix\""
580
+ ],
581
+ "next.js + turbopack": ["\"vite\""]
582
+ },
583
+ monorepo: {
584
+ "not needed": [
585
+ "\"auto\"",
586
+ "\"changesets\"",
587
+ "\"commitlint\"",
588
+ "\"conventional-changelog\"",
589
+ "\"release-it\"",
590
+ "\"semantic-release\"",
591
+ "\"standard-version\""
592
+ ],
593
+ turbo: ["\"lerna\"", "\"nx\""]
594
+ },
595
+ notifications: { "shadcn sonner": [
596
+ "\"notistack\"",
597
+ "\"react-alert\"",
598
+ "\"react-notifications\""
599
+ ] },
600
+ orm: { drizzle: [
601
+ "\"knex\"",
602
+ "\"mikro-orm\"",
603
+ "\"mongoose\"",
604
+ "\"prisma\"",
605
+ "\"sequelize\"",
606
+ "\"typeorm\""
607
+ ] },
608
+ parsing: {
609
+ [`DOMParser or bun ${w("HTMLRewriter")}`]: [
610
+ "\"fast-xml-parser\"",
611
+ "\"htmlparser2\"",
612
+ "\"xml2js\""
613
+ ],
614
+ "bun .env auto-loading": [
615
+ "\"dotenv-cli\"",
616
+ "\"dotenv-defaults\"",
617
+ "\"dotenv-expand\"",
618
+ "\"dotenv-flow\"",
619
+ "\"dotenv-safe\"",
620
+ "\"env-cmd\"",
621
+ "\"nconf\""
622
+ ],
623
+ [b("JSON5", "JSONC")]: [
624
+ "\"comment-json\"",
625
+ "\"hjson\"",
626
+ "\"json5\"",
627
+ "\"jsonc-parser\""
628
+ ],
629
+ [b("JSONL")]: ["\"jsonlines\"", "\"ndjson\""],
630
+ [b("TOML")]: [
631
+ "\"@iarna/toml\"",
632
+ "\"smol-toml\"",
633
+ "\"toml\""
634
+ ],
635
+ [b("YAML")]: ["\"js-yaml\"", "\"yaml\""],
636
+ [`${b("file")} + string split`]: [
637
+ "\"csv-parse\"",
638
+ "\"csv-stringify\"",
639
+ "\"fast-csv\"",
640
+ "\"ini\"",
641
+ "\"neat-csv\"",
642
+ "\"papaparse\"",
643
+ "\"strip-json-comments\""
644
+ ]
645
+ },
646
+ postcss: { "tailwind v4 (built-in)": [
647
+ "\"autoprefixer\"",
648
+ "\"postcss-import\"",
649
+ "\"postcss-modules\"",
650
+ "\"postcss-nesting\"",
651
+ "\"postcss-preset-env\""
652
+ ] },
653
+ process: {
654
+ [`${w("Promise")}.all or async loops`]: ["\"@supercharge/promise-pool\""],
655
+ "bun patch": ["\"patch-package\""],
656
+ lintmax: ["\"lint-staged\""],
657
+ "not needed with shadcn": ["\"storybook\""],
658
+ "process.env.CI": ["\"ci-info\"", "\"is-ci\""],
659
+ "simple-git-hooks": [
660
+ "\"husky\"",
661
+ "\"lefthook\"",
662
+ "\"pre-commit\""
663
+ ],
664
+ turbo: [
665
+ "\"concurrently\"",
666
+ "\"npm-run-all\"",
667
+ "\"npm-run-all2\"",
668
+ "\"wireit\""
669
+ ]
670
+ },
671
+ queue: {
672
+ [b("cron")]: ["\"agenda\""],
673
+ [`${b("sql")} or platform queues`]: ["\"bee-queue\"", "\"pg-boss\""]
674
+ },
675
+ reactHooks: { "React built-ins or es-toolkit": [
676
+ "\"ahooks\"",
677
+ "\"react-use\"",
678
+ "\"usehooks-ts\""
679
+ ] },
680
+ reactUtils: {
681
+ "React hooks": [
682
+ "\"react-adopt\"",
683
+ "\"react-fns\"",
684
+ "\"react-powerplug\"",
685
+ "\"react-side-effect\"",
686
+ "\"recompose\""
687
+ ],
688
+ "React.lazy or next.js": ["\"react-loadable\""],
689
+ TypeScript: ["\"create-react-class\"", "\"prop-types\""],
690
+ "cn() from shadcn": [
691
+ "\"class-variance-authority\"",
692
+ "\"classnames\"",
693
+ "\"clsx\"",
694
+ "\"cva\"",
695
+ "\"tailwind-merge\""
696
+ ],
697
+ "native CSS media queries or tailwind breakpoints": [
698
+ "\"react-device-detect\"",
699
+ "\"react-media\"",
700
+ "\"react-responsive\""
701
+ ],
702
+ "native IntersectionObserver": ["\"react-intersection-observer\""],
703
+ "native ResizeObserver": [
704
+ "\"react-measure\"",
705
+ "\"react-resize-detector\"",
706
+ "\"react-sizes\"",
707
+ "\"react-use-measure\""
708
+ ],
709
+ "native loading=lazy or React.lazy": ["\"react-lazy-load\"", "\"react-lazyload\""],
710
+ "navigator.clipboard API": ["\"react-copy-to-clipboard\""],
711
+ "next.js": [
712
+ "\"craco\"",
713
+ "\"react-app-rewired\"",
714
+ "\"react-dev-utils\"",
715
+ "\"react-hot-loader\"",
716
+ "\"react-scripts\""
717
+ ],
718
+ "next.js metadata": ["\"react-document-title\"", "\"react-helmet\""],
719
+ "next.js routing": [
720
+ "\"@reach/router\"",
721
+ "\"@tanstack/react-router\"",
722
+ "\"history\"",
723
+ "\"react-router\"",
724
+ "\"wouter\""
725
+ ],
726
+ "shadcn calendar": ["\"react-day-picker\""],
727
+ "shadcn combobox": ["\"react-select\""],
728
+ "shadcn command": ["\"cmdk\""],
729
+ "shadcn dialog": ["\"react-modal\""],
730
+ "shadcn drawer": ["\"vaul\""],
731
+ "shadcn or HTML input": ["\"react-dropzone\""],
732
+ "shadcn otp": ["\"input-otp\""],
733
+ "shadcn pagination": ["\"react-paginate\""],
734
+ "shadcn resizable": ["\"react-resizable-panels\""],
735
+ "shadcn sonner": ["\"react-hot-toast\"", "\"react-toastify\""],
736
+ "tanstack-query": ["\"react-query\"", "\"swr\""]
737
+ },
738
+ realtime: { [`${b("serve")} (WebSocket)`]: [
739
+ "\"@stomp/stompjs\"",
740
+ "\"ably\"",
741
+ "\"pusher\"",
742
+ "\"pusher-js\"",
743
+ "\"sockjs-client\""
744
+ ] },
745
+ retry: {
746
+ [w("AbortController")]: ["\"p-cancelable\""],
747
+ [`${w("AbortSignal")}.timeout`]: ["\"p-timeout\""],
748
+ [`${w("EventTarget")} + ${w("Promise")}`]: ["\"p-event\""],
749
+ [`${w("Promise")}.all or async loops`]: [
750
+ "\"async\"",
751
+ "\"async-retry\"",
752
+ "\"bottleneck\"",
753
+ "\"p-all\"",
754
+ "\"p-each-series\"",
755
+ "\"p-filter\"",
756
+ "\"p-limit\"",
757
+ "\"p-map\"",
758
+ "\"p-map-series\"",
759
+ "\"p-pipe\"",
760
+ "\"p-queue\"",
761
+ "\"p-retry\"",
762
+ "\"p-forever\"",
763
+ "\"p-lazy\"",
764
+ "\"p-min-delay\"",
765
+ "\"p-props\"",
766
+ "\"p-series\"",
767
+ "\"p-times\"",
768
+ "\"p-try\"",
769
+ "\"p-waterfall\"",
770
+ "\"p-whilst\"",
771
+ "\"exponential-backoff\"",
772
+ "\"promise-retry\"",
773
+ "\"retry\""
774
+ ],
775
+ [`${w("Promise")}.allSettled`]: ["\"p-reflect\"", "\"p-settle\""],
776
+ [`${w("Promise")}.any`]: ["\"p-any\"", "\"p-some\""],
777
+ [`${w("Promise")}.race`]: ["\"p-race\""],
778
+ "es-toolkit": [
779
+ "\"p-debounce\"",
780
+ "\"p-memoize\"",
781
+ "\"p-throttle\""
782
+ ],
783
+ [b("sleep")]: ["\"delay\""]
784
+ },
785
+ routing: { [w("URLPattern")]: ["\"path-to-regexp\""] },
786
+ runtime: {
787
+ bun: [
788
+ "\"deno\"",
789
+ "\"esno\"",
790
+ "\"jiti\"",
791
+ "\"module-alias\"",
792
+ "\"ts-node\"",
793
+ "\"tsconfig-paths\"",
794
+ "\"tsx\""
795
+ ],
796
+ "bun --watch": [
797
+ "\"nodemon\"",
798
+ "\"onchange\"",
799
+ "\"ts-node-dev\"",
800
+ "\"tsc-watch\""
801
+ ],
802
+ "bun .env auto-loading": ["\"cross-env\"", "\"dotenv\""],
803
+ "docker or bun --watch": ["\"pm2\""]
804
+ },
805
+ rxjs: {
806
+ [`${w("EventTarget")} or async iterators`]: [
807
+ "\"emittery\"",
808
+ "\"eventemitter2\"",
809
+ "\"eventemitter3\"",
810
+ "\"mitt\"",
811
+ "\"nanoevents\""
812
+ ],
813
+ [`async iterators or ${w("ReadableStream")}`]: ["\"rxjs\""]
814
+ },
815
+ sanitize: {
816
+ [b("CSRF")]: ["\"csurf\""],
817
+ [b("escapeHTML")]: [
818
+ "\"dompurify\"",
819
+ "\"sanitize-html\"",
820
+ "\"xss\""
821
+ ],
822
+ zod: ["\"validator\""]
823
+ },
824
+ shell: {
825
+ [b("$")]: [
826
+ "\"@actions/exec\"",
827
+ "\"@npmcli/run-script\"",
828
+ "\"execa\"",
829
+ "\"open\"",
830
+ "\"shelljs\"",
831
+ "\"tinyexec\"",
832
+ "\"zx\""
833
+ ],
834
+ [b("spawn")]: [
835
+ "\"cross-spawn\"",
836
+ "\"pidtree\"",
837
+ "\"tree-kill\""
838
+ ],
839
+ [b("which")]: ["\"npm-which\"", "\"which\""],
840
+ [`${n("fs")} watch`]: [
841
+ "\"chokidar\"",
842
+ "\"gaze\"",
843
+ "\"node-watch\""
844
+ ],
845
+ "process.on exit": ["\"signal-exit\""]
846
+ },
847
+ state: { "zustand or jotai": [
848
+ "\"@ngrx/store\"",
849
+ "\"@preact/signals\"",
850
+ "\"@reduxjs/toolkit\"",
851
+ "\"effector\"",
852
+ "\"immer\"",
853
+ "\"@legendapp/state\"",
854
+ "\"mobx\"",
855
+ "\"nanostores\"",
856
+ "\"proxy-state-tree\"",
857
+ "\"recoil\"",
858
+ "\"redux\"",
859
+ "\"redux-observable\"",
860
+ "\"redux-persist\"",
861
+ "\"redux-saga\"",
862
+ "\"redux-thunk\"",
863
+ "\"use-immer\"",
864
+ "\"valtio\"",
865
+ "\"xstate\""
866
+ ] },
867
+ storage: { "upstash REST or platform-managed": ["\"@vercel/kv\""] },
868
+ styling: {
869
+ "React JSX": [
870
+ "\"ejs\"",
871
+ "\"handlebars\"",
872
+ "\"hbs\"",
873
+ "\"mustache\"",
874
+ "\"nunjucks\"",
875
+ "\"pug\"",
876
+ "\"swig\""
877
+ ],
878
+ tailwind: [
879
+ "\"@pandacss/dev\"",
880
+ "\"@stitches",
881
+ "\"@stylexjs/stylex\"",
882
+ "\"css-loader\"",
883
+ "\"@emotion",
884
+ "\"react-css-modules\"",
885
+ "\"react-jss\"",
886
+ "\"less\"",
887
+ "\"linaria\"",
888
+ "\"sass\"",
889
+ "\"styled-components\"",
890
+ "\"stylus\"",
891
+ "\"twin.macro\"",
892
+ "\"unocss\"",
893
+ "\"vanilla-extract\"",
894
+ "\"windicss\""
895
+ ],
896
+ "tailwind classes": [
897
+ "\"chroma-js\"",
898
+ "\"color\"",
899
+ "\"color-convert\"",
900
+ "\"tinycolor2\""
901
+ ]
902
+ },
903
+ table: { "tanstack-table": ["\"ag-grid\"", "\"react-table\""] },
904
+ test: {
905
+ "bun:test": [
906
+ "\"@jest",
907
+ "\"ava\"",
908
+ "\"chai\"",
909
+ "\"expect.js\"",
910
+ "\"jasmine\"",
911
+ "\"jest\"",
912
+ "\"karma\"",
913
+ "\"mocha\"",
914
+ "\"power-assert\"",
915
+ "\"should\"",
916
+ "\"tap\"",
917
+ "\"vitest\""
918
+ ],
919
+ "bun:test (coverage built-in)": ["\"c8\"", "\"nyc\""],
920
+ "bun:test (mock built-in)": [
921
+ "\"@faker-js/faker\"",
922
+ "\"chance\"",
923
+ "\"faker\"",
924
+ "\"fetch-mock\"",
925
+ "\"msw\"",
926
+ "\"nock\"",
927
+ "\"sinon\""
928
+ ],
929
+ "bun:test fetch": ["\"supertest\""],
930
+ "bun:test mock": [
931
+ "\"proxyquire\"",
932
+ "\"rewire\"",
933
+ "\"testdouble\""
934
+ ]
935
+ },
936
+ tooltip: {
937
+ "cnsync (readonly/ui)": ["\"@floating-ui/react\""],
938
+ "shadcn or cnsync": ["\"react-popper\""],
939
+ "shadcn tooltip": ["\"react-tooltip\"", "\"tippy.js\""]
940
+ },
941
+ types: { "TypeScript built-in utility types": [
942
+ "\"ts-essentials\"",
943
+ "\"ts-toolbelt\"",
944
+ "\"tsutils\"",
945
+ "\"type-fest\"",
946
+ "\"utility-types\""
947
+ ] },
948
+ utils: {
949
+ "es-toolkit": [
950
+ "\"camelcase\"",
951
+ "\"change-case\"",
952
+ "\"fast-memoize\"",
953
+ "\"just-",
954
+ "\"micro-memoize\"",
955
+ "\"deepmerge\"",
956
+ "\"deepmerge-ts\"",
957
+ "\"is-plain-object\"",
958
+ "\"lodash\"",
959
+ "\"lodash-es\"",
960
+ "\"memoize-one\"",
961
+ "\"memoizee\"",
962
+ "\"radash\"",
963
+ "\"rambda\"",
964
+ "\"ramda\"",
965
+ "\"remeda\"",
966
+ "\"throttle-debounce\"",
967
+ "\"underscore\""
968
+ ],
969
+ "es-toolkit or native": [
970
+ "\"fast-levenshtein\"",
971
+ "\"leven\"",
972
+ "\"natural-compare\""
973
+ ],
974
+ "es-toolkit or regex": ["\"slugify\"", "\"speakingurl\""],
975
+ [b("deepEquals")]: [
976
+ "\"deep-equal\"",
977
+ "\"dequal\"",
978
+ "\"fast-deep-equal\""
979
+ ],
980
+ [b("inspect")]: ["\"object-inspect\""],
981
+ [b("sliceAnsi")]: ["\"slice-ansi\""],
982
+ [b("stringWidth")]: ["\"string-width\""],
983
+ [b("stripANSI")]: ["\"ansi-regex\"", "\"strip-ansi\""],
984
+ [b("wrapAnsi")]: ["\"wrap-ansi\""],
985
+ "literal numbers": ["\"http-status-codes\""],
986
+ "native Array.flat": ["\"array-flatten\"", "\"flat\""],
987
+ "native Array.from": ["\"arrify\""],
988
+ [`${w("JSON")}.parse`]: ["\"destr\""],
989
+ "native Object.hasOwn": ["\"has\""],
990
+ [`${w("RegExp")} constructor`]: ["\"escape-string-regexp\""],
991
+ [w("URL")]: ["\"normalize-url\""],
992
+ [`[...new ${w("Set")}()]`]: ["\"array-unique\"", "\"uniq\""],
993
+ [`${n("net")} createServer`]: [
994
+ "\"detect-port\"",
995
+ "\"get-port\"",
996
+ "\"portfinder\""
997
+ ],
998
+ "optional chaining": ["\"dlv\"", "\"dot-prop\""],
999
+ "process.env or node:fs check": ["\"is-docker\"", "\"is-wsl\""],
1000
+ [w("structuredClone")]: [
1001
+ "\"circular-json\"",
1002
+ "\"devalue\"",
1003
+ "\"flatted\"",
1004
+ "\"json-stringify-safe\"",
1005
+ "\"serialize-javascript\"",
1006
+ "\"superjson\""
1007
+ ],
1008
+ [`${w("structuredClone")} or es-toolkit`]: [
1009
+ "\"clone\"",
1010
+ "\"clone-deep\"",
1011
+ "\"rfdc\""
1012
+ ],
1013
+ "template literals": [
1014
+ "\"common-tags\"",
1015
+ "\"dedent\"",
1016
+ "\"endent\"",
1017
+ "\"outdent\""
1018
+ ],
1019
+ "throw new Error": ["\"invariant\""],
1020
+ typeof: ["\"is-number\"", "\"kind-of\""]
1021
+ },
1022
+ validation: { zod: [
1023
+ "\"@sinclair/typebox\"",
1024
+ "\"ajv\"",
1025
+ "\"arktype\"",
1026
+ "\"class-validator\"",
1027
+ "\"fastest-validator\"",
1028
+ "\"io-ts\"",
1029
+ "\"joi\"",
1030
+ "\"ow\"",
1031
+ "\"runtypes\"",
1032
+ "\"superstruct\"",
1033
+ "\"typebox\"",
1034
+ "\"computed-types\"",
1035
+ "\"valibot\"",
1036
+ "\"yup\""
1037
+ ] },
1038
+ versioning: { [b("semver")]: [
1039
+ "\"compare-versions\"",
1040
+ "\"node-semver\"",
1041
+ "\"semver\"",
1042
+ "\"semver-diff\"",
1043
+ "\"semver-regex\""
1044
+ ] },
1045
+ virtualization: { "native scroll or tanstack-virtual": [
1046
+ "\"react-virtualized\"",
1047
+ "\"react-virtuoso\"",
1048
+ "\"react-window\""
1049
+ ] },
1050
+ worker: { [w("Worker")]: [
1051
+ "\"comlink\"",
1052
+ "\"piscina\"",
1053
+ "\"threads\"",
1054
+ "\"tinypool\"",
1055
+ "\"worker-threads-pool\"",
1056
+ "\"workerpool\""
1057
+ ] }
1058
+ };
1059
+ const LINTMAX_ONLY_RAW = { linting: { lintmax: [
1060
+ "\"@biomejs",
1061
+ "\"eslint\"",
1062
+ "\"oxlint\"",
1063
+ "\"prettier\""
1064
+ ] } };
1065
+ const BUN_GLOBALS = Object.fromEntries([
1066
+ "$",
1067
+ "Archive",
1068
+ "ArrayBufferSink",
1069
+ "CSRF",
1070
+ "Cookie",
1071
+ "CookieMap",
1072
+ "CryptoHasher",
1073
+ "FFI",
1074
+ "FileSystemRouter",
1075
+ "Glob",
1076
+ "JSON5",
1077
+ "JSONC",
1078
+ "JSONL",
1079
+ "MD4",
1080
+ "MD5",
1081
+ "RedisClient",
1082
+ "S3Client",
1083
+ "SHA1",
1084
+ "SHA224",
1085
+ "SHA256",
1086
+ "SHA384",
1087
+ "SHA512",
1088
+ "SHA512_256",
1089
+ "SQL",
1090
+ "TOML",
1091
+ "Terminal",
1092
+ "Transpiler",
1093
+ "YAML",
1094
+ "allocUnsafe",
1095
+ "argv",
1096
+ "build",
1097
+ "color",
1098
+ "concatArrayBuffers",
1099
+ "connect",
1100
+ "cron",
1101
+ "deepEquals",
1102
+ "deepMatch",
1103
+ "deflateSync",
1104
+ "dns",
1105
+ "embeddedFiles",
1106
+ "enableANSIColors",
1107
+ "env",
1108
+ "escapeHTML",
1109
+ "fetch",
1110
+ "file",
1111
+ "fileURLToPath",
1112
+ "gc",
1113
+ "generateHeapSnapshot",
1114
+ "gunzipSync",
1115
+ "gzipSync",
1116
+ "hash",
1117
+ "indexOfLine",
1118
+ "inflateSync",
1119
+ "inspect",
1120
+ "isMainThread",
1121
+ "listen",
1122
+ "main",
1123
+ "markdown",
1124
+ "mmap",
1125
+ "nanoseconds",
1126
+ "openInEditor",
1127
+ "password",
1128
+ "pathToFileURL",
1129
+ "peek",
1130
+ "plugin",
1131
+ "postgres",
1132
+ "randomUUIDv5",
1133
+ "randomUUIDv7",
1134
+ "readableStreamToArray",
1135
+ "readableStreamToArrayBuffer",
1136
+ "readableStreamToBlob",
1137
+ "readableStreamToBytes",
1138
+ "readableStreamToFormData",
1139
+ "readableStreamToJSON",
1140
+ "readableStreamToText",
1141
+ "redis",
1142
+ "resolve",
1143
+ "resolveSync",
1144
+ "revision",
1145
+ "s3",
1146
+ "secrets",
1147
+ "semver",
1148
+ "serve",
1149
+ "sha",
1150
+ "shrink",
1151
+ "sleep",
1152
+ "sleepSync",
1153
+ "sliceAnsi",
1154
+ "spawn",
1155
+ "spawnSync",
1156
+ "sql",
1157
+ "stderr",
1158
+ "stdin",
1159
+ "stdout",
1160
+ "stringWidth",
1161
+ "stripANSI",
1162
+ "udpSocket",
1163
+ "unsafe",
1164
+ "version",
1165
+ "which",
1166
+ "wrapAnsi",
1167
+ "write",
1168
+ "zstdCompress",
1169
+ "zstdCompressSync",
1170
+ "zstdDecompress",
1171
+ "zstdDecompressSync"
1172
+ ].map((name) => [`Bun.${name}`, `import { ${name} } from 'bun'`]));
1173
+ const TEMPORARY = new Set([
1174
+ "\"@t3-oss/env",
1175
+ "\"@upstash/ratelimit\"",
1176
+ "\"@upstash/redis\"",
1177
+ "\"clsx\"",
1178
+ "\"dotenv-cli\"",
1179
+ "\"ioredis\"",
1180
+ "\"jose\"",
1181
+ "\"pg-boss\"",
1182
+ "\"pino\"",
1183
+ "\"postgres\"",
1184
+ "\"q\"",
1185
+ "\"react-dropzone\"",
1186
+ "\"react-intersection-observer\"",
1187
+ "\"sharp\"",
1188
+ "\"slugify\"",
1189
+ "\"smol-toml\"",
1190
+ "\"tailwind-merge\""
1191
+ ]);
1192
+ const flattenBanned = (dict) => Object.values(dict).flatMap((fixes) => Object.entries(fixes).flatMap(([fix, bans]) => bans.map((ban) => ({
1193
+ ban,
1194
+ fix
1195
+ }))));
1196
+ const ALL_BANNED = flattenBanned(BANNED);
1197
+ const LINTMAX_ONLY = flattenBanned(LINTMAX_ONLY_RAW);
1198
+ //#endregion
1199
+ //#region src/checks.ts
1200
+ const SCAN_EXCLUDE = new Set([
1201
+ ".git",
1202
+ ".next",
1203
+ ".turbo",
1204
+ ".vercel",
1205
+ "dist",
1206
+ "node_modules",
1207
+ "readonly",
1208
+ "templates"
1209
+ ]);
1210
+ const glob = async (pattern, cwd) => {
1211
+ const results = [];
1212
+ const dot = pattern.includes("/.");
1213
+ for await (const f of new Glob(pattern).scan({
1214
+ cwd,
1215
+ dot,
1216
+ onlyFiles: true
1217
+ })) if (!f.split("/").some((seg) => SCAN_EXCLUDE.has(seg))) results.push(join(cwd, f));
1218
+ return results;
1219
+ };
1220
+ const shell = async (projectPath, ...args) => {
1221
+ return (await $`rg ${args} ${projectPath} -g '*.ts' -g '*.tsx' ${RG_EXCLUDE} -l`.quiet().nothrow()).stdout.toString().trim();
1222
+ };
1223
+ const importPattern = (ban) => {
1224
+ const exact = ban.endsWith("\"");
1225
+ return `(from|import|require)[\\s(]+['"]${ban.replaceAll(/^"|"$/gu, "").replaceAll(/[.*+?^${}()|[\]\\]/gu, String.raw`\$&`)}${exact ? `['"]` : ""}`;
1226
+ };
1227
+ const relList = (files, base) => files.split("\n").map((f) => rel(f, base)).join(", ");
1228
+ const issue = (type, detail) => ({
1229
+ detail,
1230
+ type
1231
+ });
1232
+ const drift = (detail) => issue("drift", detail);
1233
+ const forbidden = (detail) => issue("forbidden", detail);
1234
+ const providerJsxRe = /<\w+Provider/gu;
1235
+ const serverProviderRe = /<\w*Server\w*Provider/u;
1236
+ const providerImportRe = /from\s+['"].*providers/u;
1237
+ const checkCi = async (projectPath) => {
1238
+ const repo = await getGhRepo(projectPath);
1239
+ if (!repo) return [];
1240
+ const result = await $`gh run list --repo ${repo} --limit 1 --json conclusion,createdAt --jq '.[0] | "\(.conclusion) \(.createdAt)"'`.quiet().nothrow();
1241
+ if (result.exitCode !== 0) {
1242
+ debug("command failed:", `gh run list --repo ${repo}`);
1243
+ return [];
1244
+ }
1245
+ const [conclusion, time] = result.stdout.toString().trim().split(" ");
1246
+ if (conclusion === "failure") return [issue("ci", `failed ${time ?? ""}`)];
1247
+ if (conclusion === "success") return [issue("info", `passed ${time ?? ""}`)];
1248
+ return [];
1249
+ };
1250
+ const checkGit = async (projectPath) => {
1251
+ if (!(await $`git status --porcelain`.cwd(projectPath).quiet().nothrow()).stdout.toString().trim()) await $`git pull --rebase`.cwd(projectPath).quiet().nothrow();
1252
+ const result = await $`git status --porcelain`.cwd(projectPath).quiet().nothrow();
1253
+ if (result.exitCode !== 0) debug("command failed:", "git status --porcelain");
1254
+ const out = result.stdout.toString().trim();
1255
+ if (!out) return [];
1256
+ return [issue("git", `${out.split("\n").length} uncommitted changes`)];
1257
+ };
1258
+ const checkDrift = async (selfPath, projectPath) => {
1259
+ const managed = await resolveManagedFiles(projectPath);
1260
+ return (await Promise.all(managed.map(async ({ extendable, path: name }) => {
1261
+ const src = file(join(selfPath, name));
1262
+ const dst = file(join(projectPath, name));
1263
+ if (!await src.exists()) return;
1264
+ if (!await dst.exists()) return issue("file", `${name} missing`);
1265
+ const [s, d] = await Promise.all([src.text(), dst.text()]);
1266
+ if (extendable) {
1267
+ if (!isExtended(s, d)) return issue("file", `${name} out of sync`);
1268
+ return;
1269
+ }
1270
+ if (s !== d) return issue("file", `${name} out of sync`);
1271
+ }))).filter((r) => r !== void 0);
1272
+ };
1273
+ const checkRootPkg = async (projectPath) => {
1274
+ const issues = [];
1275
+ const pkg = await readPkg(join(projectPath, "package.json"));
1276
+ if (!pkg) return issues;
1277
+ if (!pkg.private) issues.push(drift("root package.json should be private"));
1278
+ if (!pkg.packageManager) issues.push(issue("missing", "packageManager field missing"));
1279
+ if (!pkg["simple-git-hooks"]) issues.push(issue("missing", "simple-git-hooks in package.json"));
1280
+ else if (!pkg["simple-git-hooks"]["pre-commit"]?.endsWith(EXPECTED.preCommit)) issues.push(drift(`pre-commit should end with "${EXPECTED.preCommit}"`));
1281
+ if (pkg.scripts?.prepare !== DEFAULT_SCRIPTS.prepare) issues.push(drift(`prepare should be "${DEFAULT_SCRIPTS.prepare}"`));
1282
+ if (!pkg.scripts?.postinstall?.includes("sherif")) issues.push(drift(`postinstall should include "${DEFAULT_SCRIPTS.postinstall}"`));
1283
+ if (pkg.scripts?.clean && !pkg.scripts.clean.startsWith(DEFAULT_SCRIPTS.clean)) issues.push(drift(`clean should start with "${DEFAULT_SCRIPTS.clean}"`));
1284
+ return issues;
1285
+ };
1286
+ const checkConfigs = async (projectPath) => {
1287
+ const issues = [];
1288
+ const caps = await detectCapabilities(projectPath);
1289
+ for (const entry of MUST_EXIST_FILES) if (!existsSync(join(projectPath, entry))) issues.push(issue("missing", entry));
1290
+ for (const c of CONDITIONAL_MUST_EXIST_FILES) if (caps[c.when] && !existsSync(join(projectPath, c.path))) issues.push(issue("missing", c.path));
1291
+ const pkg = await readPkg(join(projectPath, "package.json"));
1292
+ if (pkg && !pkg.scripts?.action) issues.push(issue("missing", "\"action\" script missing"));
1293
+ const ts = await readJson(join(projectPath, "tsconfig.json"));
1294
+ if (ts) {
1295
+ if (ts.extends && ts.extends !== EXPECTED.tsconfigExtends) issues.push(drift("tsconfig.json should extend lintmax/tsconfig"));
1296
+ if (ts.include) issues.push(drift("root tsconfig.json should not have \"include\""));
1297
+ if (!getTsconfigTypes(ts)?.includes("bun-types")) issues.push(issue("missing", "root tsconfig.json missing \"bun-types\""));
1298
+ }
1299
+ const v = await readJson(join(projectPath, "vercel.json"));
1300
+ if (v?.installCommand && v.installCommand !== EXPECTED.vercelInstall) issues.push(drift("vercel.json installCommand should be \"bun i\""));
1301
+ return issues;
1302
+ };
1303
+ const MIN_H_RE = /min-h-(?<size>screen|dvh)/u;
1304
+ const NESTED_GITIGNORE_ALLOW_RE = /^(?<dir>\.convex|data|fixtures|seeds|snapshots|infra)\/\.gitignore$/u;
1305
+ const LAYOUT_REQUIRED = [
1306
+ ["suppressHydrationWarning", "missing suppressHydrationWarning on <html>"],
1307
+ ["antialiased", "missing antialiased on <body>"],
1308
+ ["tracking-[-0.02em]", "missing tracking-[-0.02em] on <html>"],
1309
+ [MIN_H_RE, "missing min-h-screen or min-h-dvh on <body>"],
1310
+ ["font-sans", "missing font-sans on <html>"],
1311
+ ["Metadata", "missing metadata export"]
1312
+ ];
1313
+ const LAYOUT_FORBIDDEN = [["RootLayout", "use Layout not RootLayout"], ["export default function", "use arrow function + export default Layout"]];
1314
+ const has = (content, check) => typeof check === "string" ? content.includes(check) : check.test(content);
1315
+ const checkContent = ({ content, must, mustNot, r }) => {
1316
+ const issues = [];
1317
+ for (const [check, msg] of must) if (!has(content, check)) issues.push(drift(`${msg}: ${r}`));
1318
+ for (const [check, msg] of mustNot) if (content.includes(check)) issues.push(drift(`${msg}: ${r}`));
1319
+ return issues;
1320
+ };
1321
+ const checkLayouts = async (projectPath) => {
1322
+ const issues = [];
1323
+ const files = await glob("**/app/layout.tsx", projectPath);
1324
+ await Promise.all(files.map(async (layoutFile) => {
1325
+ const content = await file(layoutFile).text();
1326
+ const r = rel(layoutFile, projectPath);
1327
+ if (!content.includes("<html")) return;
1328
+ issues.push(...checkContent({
1329
+ content,
1330
+ must: LAYOUT_REQUIRED,
1331
+ mustNot: LAYOUT_FORBIDDEN,
1332
+ r
1333
+ }));
1334
+ if (content.includes("'./globals.css'") || content.includes("\"./globals.css\"")) issues.push(drift(`use global.css not globals.css: ${r}`));
1335
+ const dir = layoutFile.replace("/layout.tsx", "");
1336
+ if (!existsSync(join(dir, "fonts.ts"))) issues.push(drift(`missing fonts.ts next to layout: ${r}`));
1337
+ if (content.includes("Providers") && !existsSync(join(dir, "providers.tsx"))) issues.push(drift(`providers.tsx should be next to layout: ${r}`));
1338
+ if ((content.match(providerJsxRe) ?? []).some((m) => !serverProviderRe.test(m)) && !providerImportRe.test(content)) issues.push(drift(`Provider in layout, move to providers.tsx: ${r}`));
1339
+ }));
1340
+ return issues;
1341
+ };
1342
+ const checkPages = async (projectPath) => {
1343
+ const issues = [];
1344
+ const files = await glob("**/app/**/page.tsx", projectPath);
1345
+ await Promise.all(files.map(async (pageFile) => {
1346
+ if ((await file(pageFile).text()).includes("export default function")) issues.push(drift(`use arrow function + export default Page: ${rel(pageFile, projectPath)}`));
1347
+ }));
1348
+ return issues;
1349
+ };
1350
+ const FD_CLASS_RE = /\b(?<util>bg|text|border|ring|fill|stroke|from|to|via|outline|divide|placeholder|decoration|caret|accent|shadow)-fd-/u;
1351
+ const checkShadcnClasses = async (projectPath) => {
1352
+ const issues = [];
1353
+ const files = await glob("**/*.tsx", projectPath);
1354
+ await Promise.all(files.map(async (tsxFile) => {
1355
+ const content = await file(tsxFile).text();
1356
+ if (FD_CLASS_RE.test(content)) issues.push(drift(`use shadcn semantic classes, not fd-* aliases: ${rel(tsxFile, projectPath)}`));
1357
+ }));
1358
+ return issues;
1359
+ };
1360
+ const PINNED_VERSION_RE = /\d+\.\d+/u;
1361
+ const ALLOWED_VERSION_RE = /^(?:workspace:|catalog:|npm:|link:|file:)/u;
1362
+ const VERSION_RE = /(?<major>\d+)\.(?<minor>\d+)(?:\.(?<patch>\d+))?/u;
1363
+ const versionTriple = (v) => {
1364
+ const g = VERSION_RE.exec(v)?.groups;
1365
+ return [
1366
+ Number(g?.major ?? 0),
1367
+ Number(g?.minor ?? 0),
1368
+ Number(g?.patch ?? 0)
1369
+ ];
1370
+ };
1371
+ const gteVersion = (a, b) => a[0] === b[0] ? a[1] === b[1] ? a[2] >= b[2] : a[1] > b[1] : a[0] > b[0];
1372
+ const fetchLatest = async (name) => {
1373
+ try {
1374
+ const res = await fetch(`https://registry.npmjs.org/${name}/latest`, { signal: AbortSignal.timeout(1e4) });
1375
+ if (!res.ok) return;
1376
+ return (await res.json()).version;
1377
+ } catch {
1378
+ debug("checkDepsLatest fetch failed:", name);
1379
+ }
1380
+ };
1381
+ const latestCache = /* @__PURE__ */ new Map();
1382
+ const checkDepsLatest = async (projectPath) => {
1383
+ const files = await glob("**/package.json", projectPath);
1384
+ const candidates = [];
1385
+ await Promise.all(files.map(async (pkgFile) => {
1386
+ const pkg = await readPkg(pkgFile);
1387
+ if (!pkg) return;
1388
+ const r = rel(pkgFile, projectPath);
1389
+ for (const field of ["dependencies", "devDependencies"]) for (const [name, version] of Object.entries(pkg[field] ?? {})) if (typeof version === "string" && PINNED_VERSION_RE.test(version) && !ALLOWED_VERSION_RE.test(version)) candidates.push({
1390
+ name,
1391
+ r,
1392
+ version
1393
+ });
1394
+ }));
1395
+ const latestByName = /* @__PURE__ */ new Map();
1396
+ await Promise.all([...new Set(candidates.map((c) => c.name))].map(async (name) => {
1397
+ const cached = latestCache.get(name) ?? fetchLatest(name);
1398
+ latestCache.set(name, cached);
1399
+ latestByName.set(name, await cached);
1400
+ }));
1401
+ const issues = [];
1402
+ for (const c of candidates) {
1403
+ const latest = latestByName.get(c.name);
1404
+ if (latest !== void 0 && gteVersion(versionTriple(latest), versionTriple(c.version))) issues.push(drift(`${c.name}@${c.version} pinned, use "latest" (latest ${latest}): ${c.r}`));
1405
+ }
1406
+ return issues;
1407
+ };
1408
+ const DEFAULT_EXPORT_RE = /export default (?<id>\w+)/u;
1409
+ const delegatesConfig = (content) => {
1410
+ const id = DEFAULT_EXPORT_RE.exec(content)?.groups?.id;
1411
+ return id !== void 0 && content.split("\n").some((line) => line.trimStart().startsWith("import") && line.includes(id));
1412
+ };
1413
+ const checkNextConfigs = async (projectPath) => {
1414
+ const issues = [];
1415
+ const configs = await glob("**/next.config.ts", projectPath);
1416
+ await Promise.all(configs.map(async (configFile) => {
1417
+ const content = await file(configFile).text();
1418
+ if (!(content.includes("reactStrictMode") || content.includes("createNextConfig") || delegatesConfig(content))) issues.push(drift(`missing reactStrictMode in ${rel(configFile, projectPath)}`));
1419
+ }));
1420
+ for (const f of await glob("**/apps/*/postcss.config.*", projectPath)) issues.push(drift(`redundant ${rel(f, projectPath)}, remove it`));
1421
+ return issues;
1422
+ };
1423
+ const checkAppTsconfigs = async (projectPath) => {
1424
+ const issues = [];
1425
+ const files = await glob("**/apps/*/tsconfig.json", projectPath);
1426
+ await Promise.all(files.map(async (tsconfigFile) => {
1427
+ const content = await file(tsconfigFile).text();
1428
+ const r = rel(tsconfigFile, projectPath);
1429
+ if (!content.includes("\"extends\"")) issues.push(drift(`${r} should extend lintmax/tsconfig`));
1430
+ if (content.includes("\"include\"")) issues.push(drift(`${r} should not have "include"`));
1431
+ }));
1432
+ return issues;
1433
+ };
1434
+ const checkForbidden = async (projectPath) => {
1435
+ const issues = [];
1436
+ for (const f of FORBIDDEN_LOCKFILES) if (existsSync(join(projectPath, f))) issues.push(forbidden(`${f} found, use bun only`));
1437
+ const [bunLockTracked, tsNoCheck] = await Promise.all([$`git ls-files bun.lock`.cwd(projectPath).quiet().nothrow(), $`rg '^// @ts-nocheck|^/\* @ts-nocheck' ${projectPath} -g '*.ts' -g '*.tsx' ${RG_EXCLUDE} -l`.quiet().nothrow()]);
1438
+ if (bunLockTracked.stdout.toString().trim()) issues.push(forbidden("bun.lock tracked in git, should be gitignored"));
1439
+ const nested = (await glob("**/.gitignore", projectPath)).filter((f) => f !== join(projectPath, ".gitignore")).filter((f) => !NESTED_GITIGNORE_ALLOW_RE.test(rel(f, projectPath)));
1440
+ if (nested.length > 0) issues.push(drift(`nested .gitignore: ${nested.map((f) => rel(f, projectPath)).join(", ")}`));
1441
+ if ((await glob("**/postcss.config.mjs", projectPath)).length > 0) issues.push(drift("postcss.config.mjs should be .ts"));
1442
+ const tsNoCheckFiles = tsNoCheck.stdout.toString().trim();
1443
+ if (tsNoCheckFiles) issues.push(forbidden(`@ts-nocheck in: ${relList(tsNoCheckFiles, projectPath)}`));
1444
+ return issues;
1445
+ };
1446
+ const checkBannedImports = async (projectPath) => {
1447
+ const issues = [];
1448
+ const isLintmax = projectPath.includes("/lintmax");
1449
+ const banned = [...ALL_BANNED, ...isLintmax ? [] : LINTMAX_ONLY].filter((b) => !TEMPORARY.has(b.ban));
1450
+ const sourceResults = await Promise.all(banned.map(async ({ ban, fix }) => {
1451
+ const files = await shell(projectPath, importPattern(ban));
1452
+ if (files) return {
1453
+ ban,
1454
+ files,
1455
+ fix
1456
+ };
1457
+ }));
1458
+ const pkgJsonFiles = await glob("**/package.json", projectPath);
1459
+ const pkgResults = await Promise.all(pkgJsonFiles.map(async (pkgPath) => {
1460
+ const raw = await readPkg(pkgPath);
1461
+ if (!raw) return [];
1462
+ const depNames = Object.keys({
1463
+ ...raw.dependencies,
1464
+ ...raw.devDependencies,
1465
+ ...raw.peerDependencies
1466
+ });
1467
+ return banned.filter(({ ban }) => {
1468
+ const clean = ban.replaceAll(/^"|"$/gu, "");
1469
+ return ban.endsWith("\"") ? depNames.includes(clean) : depNames.some((d) => d.startsWith(clean));
1470
+ }).map(({ ban, fix }) => ({
1471
+ ban,
1472
+ files: pkgPath,
1473
+ fix
1474
+ }));
1475
+ }));
1476
+ for (const r of [...sourceResults, ...pkgResults.flat()]) if (r) issues.push(forbidden(`${r.ban} banned, use ${r.fix}: ${relList(r.files, projectPath)}`));
1477
+ const hasNext = (await Promise.all(pkgJsonFiles.map(async (pkgPath) => {
1478
+ const raw = await readPkg(pkgPath);
1479
+ return raw ? Object.keys({
1480
+ ...raw.dependencies,
1481
+ ...raw.devDependencies,
1482
+ ...raw.peerDependencies
1483
+ }).includes("next") : false;
1484
+ }))).some(Boolean);
1485
+ const bunGlobals = (await $`rg 'Bun\.\w+' ${projectPath} -g '*.ts' -g '*.tsx' -g '!*.d.ts' ${RG_EXCLUDE} -o --no-filename`.quiet().nothrow()).stdout.toString().trim();
1486
+ if (bunGlobals && !hasNext) {
1487
+ const fixable = [...new Set(bunGlobals.split("\n"))].filter((g) => BUN_GLOBALS[g]);
1488
+ if (fixable.length > 0) issues.push(forbidden(`use named imports: ${fixable.map((g) => `${g} → ${BUN_GLOBALS[g]}`).join(", ")}`));
1489
+ }
1490
+ const deepUi = await shell(projectPath, `${UI_PACKAGE_NAME}/lib/`);
1491
+ if (deepUi) issues.push(forbidden(`use import { cn } from '${UI_PACKAGE_NAME}', not deep paths: ${relList(deepUi, projectPath)}`));
1492
+ return issues;
1493
+ };
1494
+ const checkVercel = async (projectPath) => {
1495
+ if (!existsSync(join(projectPath, ".vercel"))) return [];
1496
+ const result = await $`bunx vercel@latest ls`.cwd(projectPath).quiet().nothrow();
1497
+ if (result.exitCode !== 0) {
1498
+ debug("command failed:", "bunx vercel@latest ls");
1499
+ return [];
1500
+ }
1501
+ if (result.stdout.toString().trim().split("\n").find((l) => l.includes("●"))?.includes("● Error")) return [issue("deploy", "vercel deployment failed")];
1502
+ return [];
1503
+ };
1504
+ const FUMADOCS_NEXT_BUILD_RE = /(?<!bunx --bun )\bnext build\b/u;
1505
+ const FUMADOCS_MDX_BARE_RE = /(?<!bunx --bun )\bfumadocs-mdx\b/u;
1506
+ const checkFumadocsBuild = async (projectPath) => {
1507
+ const issues = [];
1508
+ const pkgFiles = await glob("**/package.json", projectPath);
1509
+ const pkgResults = await Promise.all(pkgFiles.map(async (pkgPath) => {
1510
+ const pkg = await readPkg(pkgPath);
1511
+ if (!pkg) return;
1512
+ if ({
1513
+ ...pkg.dependencies,
1514
+ ...pkg.devDependencies
1515
+ }["fumadocs-ui"]) return {
1516
+ path: pkgPath,
1517
+ scripts: pkg.scripts ?? {}
1518
+ };
1519
+ }));
1520
+ for (const entry of pkgResults.filter((e) => e !== void 0)) for (const [name, script] of Object.entries(entry.scripts)) if (FUMADOCS_NEXT_BUILD_RE.test(script) || FUMADOCS_MDX_BARE_RE.test(script)) issues.push(drift(`${rel(entry.path, projectPath)} script "${name}" missing bunx --bun prefix`));
1521
+ return issues;
1522
+ };
1523
+ const FUMADOCS_INLINE_NAV_RE = /<(?:DocsLayout|HomeLayout)[^>]*\bnav=\{/u;
1524
+ const ALIAS_TRIM_TAIL_RE = /\/\*$/u;
1525
+ const ALIAS_DOT_SLASH_RE = /^\.\//u;
1526
+ const checkFumadocsGithubUrl = async (projectPath) => {
1527
+ const pkgFiles = await glob("**/package.json", projectPath);
1528
+ const fumadocsDirs = (await Promise.all(pkgFiles.map(async (pkgPath) => {
1529
+ const pkg = await readPkg(pkgPath);
1530
+ if (!pkg) return;
1531
+ return {
1532
+ ...pkg.dependencies,
1533
+ ...pkg.devDependencies
1534
+ }["fumadocs-ui"] ? join(pkgPath, "..") : void 0;
1535
+ }))).filter((d) => d !== void 0);
1536
+ return (await Promise.all(fumadocsDirs.map(async (appDir) => {
1537
+ const dirIssues = [];
1538
+ const aliasTarget = ((await readJson(join(appDir, "tsconfig.json")))?.compilerOptions)?.paths?.["@/*"]?.[0];
1539
+ const libBase = aliasTarget ? aliasTarget.replace(ALIAS_TRIM_TAIL_RE, "").replace(ALIAS_DOT_SLASH_RE, "") || "." : "src";
1540
+ const sharedPath = join(appDir, libBase === "." ? "lib/layout.shared.tsx" : `${libBase}/lib/layout.shared.tsx`);
1541
+ if (existsSync(sharedPath)) {
1542
+ if (!(await file(sharedPath).text()).includes("githubUrl:")) dirIssues.push(drift(`${rel(sharedPath, projectPath)} missing githubUrl in baseOptions`));
1543
+ } else dirIssues.push(drift(`${rel(sharedPath, projectPath)} missing (fumadocs SSOT)`));
1544
+ const tsxFiles = await glob("**/*.tsx", appDir);
1545
+ const tsxIssues = await Promise.all(tsxFiles.map(async (tsxPath) => {
1546
+ const content = await file(tsxPath).text();
1547
+ if (!content.includes("fumadocs-ui/layouts")) return;
1548
+ if (FUMADOCS_INLINE_NAV_RE.test(content) && !content.includes("baseOptions")) return drift(`${rel(tsxPath, projectPath)} uses inline nav={...}; spread {...baseOptions()} instead`);
1549
+ }));
1550
+ for (const t of tsxIssues) if (t) dirIssues.push(t);
1551
+ return dirIssues;
1552
+ }))).flat();
1553
+ };
1554
+ const checkMergeMarkers = async (projectPath) => {
1555
+ const out = (await $`rg -l --multiline-dotall -n '^(<{7}|={7}|>{7})' ${projectPath} ${RG_EXCLUDE}`.quiet().nothrow()).stdout.toString().trim();
1556
+ if (!out) return [];
1557
+ return [forbidden(`unresolved merge markers in: ${relList(out, projectPath)}`)];
1558
+ };
1559
+ const ENV_LINE_RE = /^\s*(?<key>[A-Za-z_][A-Za-z0-9_]*)\s*=/u;
1560
+ const GENERATED_API_RE = /\/_generated\/api\.d\.ts$/u;
1561
+ const ENV_CANDIDATES = [
1562
+ "apps/backend/.env",
1563
+ ".env",
1564
+ "apps/convex/.env"
1565
+ ];
1566
+ const parseEnvKeys = (text) => {
1567
+ const keys = /* @__PURE__ */ new Set();
1568
+ for (const line of text.split("\n")) {
1569
+ const key = ENV_LINE_RE.exec(line)?.groups?.key;
1570
+ if (key && !line.trim().startsWith("#")) keys.add(key);
1571
+ }
1572
+ return keys;
1573
+ };
1574
+ const checkConvexSelfHosted = async (projectPath) => {
1575
+ const issues = [];
1576
+ const generated = await glob("**/convex/_generated/api.d.ts", projectPath);
1577
+ if (generated.length === 0) return issues;
1578
+ const envHit = (await Promise.all(ENV_CANDIDATES.map(async (cand) => {
1579
+ const p = join(projectPath, cand);
1580
+ if (!existsSync(p)) return null;
1581
+ const text = await file(p).text();
1582
+ return text.includes("CONVEX_SELF_HOSTED_URL") ? {
1583
+ p,
1584
+ text
1585
+ } : null;
1586
+ }))).find((e) => e !== null);
1587
+ if (!envHit) return issues;
1588
+ const envKeys = parseEnvKeys(envHit.text);
1589
+ const convexDirs = generated.map((g) => g.replace(GENERATED_API_RE, ""));
1590
+ const [nodeEnvHits, setHits, pkgFiles] = await Promise.all([
1591
+ Promise.all(convexDirs.map(async (d) => $`rg -l "process\.env(\.NODE_ENV|\['NODE_ENV'\]|\[\"NODE_ENV\"\]).*['\"]production['\"]" ${d} -g '*.ts' -g '*.tsx' -g '!_generated/**' -g '!*.test.ts' ${RG_EXCLUDE}`.quiet().nothrow())),
1592
+ $`rg -l 'convex env set' ${projectPath} -g '*.ts' -g '*.tsx' -g '!**/sync*.ts' -g '!**/scripts/test-*.ts' -g '!**/global-setup*.ts' ${RG_EXCLUDE}`.quiet().nothrow(),
1593
+ glob("**/package.json", projectPath)
1594
+ ]);
1595
+ for (const r of nodeEnvHits) {
1596
+ const out = r.stdout.toString().trim();
1597
+ if (out) issues.push(forbidden(`NODE_ENV === 'production' branch in convex/ (always true on self-hosted; gate on explicit env flag instead): ${relList(out, projectPath)}`));
1598
+ }
1599
+ const setOut = setHits.stdout.toString().trim();
1600
+ if (setOut) issues.push(forbidden(`'convex env set' outside sync.ts (.env is source of truth): ${relList(setOut, projectPath)}`));
1601
+ if ((await Promise.all(pkgFiles.map(async (p) => readPkg(p)))).some((pkg) => Boolean({
1602
+ ...pkg?.dependencies,
1603
+ ...pkg?.devDependencies
1604
+ }["@convex-dev/auth"]))) {
1605
+ for (const k of ["JWT_PRIVATE_KEY", "JWKS"]) if (!envKeys.has(k)) issues.push(issue("missing", `${k} required by @convex-dev/auth in ${rel(envHit.p, projectPath)}`));
1606
+ }
1607
+ if (!envKeys.has("SITE_URL")) issues.push(issue("missing", `SITE_URL required for Convex auth callbacks in ${rel(envHit.p, projectPath)}`));
1608
+ return issues;
1609
+ };
1610
+ //#endregion
1611
+ //#region src/format.ts
1612
+ const shellMetaRe = /[^\w\s./:@=-]/gu;
1613
+ const shellEscape = (s) => s.replaceAll(shellMetaRe, String.raw`\$&`);
1614
+ const isInfoOnly = (i) => i.type === "info" || i.type === "check" && !i.detail.startsWith("failed");
1615
+ const hasRealIssues = (issues) => issues.some((i) => !isInfoOnly(i));
1616
+ const formatIssues = (projectPath, issues) => {
1617
+ if (issues.length === 0) return projectPath;
1618
+ return [projectPath, ...issues.map((issue) => ` ${issue.type} ${issue.detail}`)].join("\n");
1619
+ };
1620
+ const timeAgo = (iso) => {
1621
+ const ms = Date.now() - new Date(iso).getTime();
1622
+ const mins = Math.floor(ms / 6e4);
1623
+ if (mins < 60) return `${mins}m ago`;
1624
+ const hours = Math.floor(mins / 60);
1625
+ if (hours < 24) return `${hours}h ago`;
1626
+ return `${Math.floor(hours / 24)}d ago`;
1627
+ };
1628
+ const getUiSyncTime = async (allPaths) => {
1629
+ if (allPaths.map((p) => join(p, "readonly", "ui", "src")).filter((d) => existsSync(d)).length === 0) return "?";
1630
+ const out = (await $`git log -1 --format=%ci -- readonly/ui`.cwd(allPaths.find((p) => existsSync(join(p, "readonly", "ui", "src"))) ?? "").quiet().nothrow()).stdout.toString().trim();
1631
+ return out ? timeAgo(new Date(out).toISOString()) : "?";
1632
+ };
1633
+ const formatSwiftBar = async (allIssues) => {
1634
+ const anyReal = [...allIssues.values()].some(hasRealIssues);
1635
+ const total = allIssues.size;
1636
+ const clean = [...allIssues.values()].filter((i) => !hasRealIssues(i)).length;
1637
+ const totalIssues = [...allIssues.values()].flatMap((i) => i.filter((x) => x.type !== "info")).length;
1638
+ const paths = [...allIssues.keys()];
1639
+ const [repos, bunVer, uiSync] = await Promise.all([
1640
+ Promise.all(paths.map(async (p) => getGhRepo(p))),
1641
+ getBunVersion(),
1642
+ getUiSyncTime(paths)
1643
+ ]);
1644
+ const repoMap = new Map(paths.map((p, i) => [p, repos[i]]));
1645
+ const lines = [];
1646
+ if (anyReal) lines.push(`${clean}/${total} | sfimage=xmark.circle.fill sfcolor=red`);
1647
+ else lines.push(`${total}/${total} | sfimage=checkmark.circle.fill sfcolor=green`);
1648
+ const f = SWIFTBAR_FONT;
1649
+ lines.push("---");
1650
+ lines.push(`${total} projects ${totalIssues} issues bun ${bunVer} ui ${uiSync} ${f}`);
1651
+ lines.push("---");
1652
+ const maxName = Math.max(...[...allIssues.keys()].map((p) => projectName(p).length));
1653
+ const allIssueLines = [];
1654
+ for (const [path, issues] of allIssues) {
1655
+ const name = projectName(path).padEnd(maxName);
1656
+ const repo = repoMap.get(path);
1657
+ const ghUrl = repo ? `https://github.com/${repo}` : "";
1658
+ const ciTimestamp = issues.find((i) => i.type === "info" || i.type === "ci")?.detail.split(" ").at(-1) ?? "";
1659
+ const ciTime = (ciTimestamp && !Number.isNaN(new Date(ciTimestamp).getTime()) ? timeAgo(ciTimestamp) : "").padEnd(6);
1660
+ const realIssues = issues.filter((i) => i.type !== "info");
1661
+ const mark = realIssues.length > 0 ? "🔴" : "🟢";
1662
+ const warn = realIssues.length > 0 ? ` ${realIssues.length} issues` : "";
1663
+ lines.push(`${mark} ${name} ${ciTime}${warn} ${f}`);
1664
+ if (realIssues.length > 0) for (const issue of realIssues) {
1665
+ lines.push(`--${issue.detail} ${f} color=#ff6b6b`);
1666
+ allIssueLines.push(`${name.trim()}: ${issue.detail}`);
1667
+ }
1668
+ if (ghUrl) lines.push(`--GitHub | href=${ghUrl}`);
1669
+ lines.push(`--VS Code | bash=/usr/bin/open param1=-a param2=Visual\\ Studio\\ Code param3=${path} terminal=false`);
1670
+ lines.push(`--Ghostty | bash=/usr/bin/open param1=-a param2=Ghostty param3=--working-directory=${path} terminal=false`);
1671
+ if (realIssues.length > 0) {
1672
+ const issueText = realIssues.map((i) => shellEscape(i.detail)).join(String.raw`\n`);
1673
+ lines.push(`--Copy Issues | bash=/bin/bash param1=-c param2='echo "${issueText}" | pbcopy' terminal=false`);
1674
+ }
1675
+ }
1676
+ if (totalIssues > 0) {
1677
+ lines.push("---");
1678
+ const allText = allIssueLines.map((l) => shellEscape(l)).join(String.raw`\n`);
1679
+ lines.push(`Copy All Issues (${totalIssues}) | bash=/bin/bash param1=-c param2='echo "${allText}" | pbcopy' terminal=false`);
1680
+ }
1681
+ lines.push("---");
1682
+ lines.push("Refresh | refresh=true");
1683
+ return lines.join("\n");
1684
+ };
1685
+ //#endregion
1686
+ //#region src/status.ts
1687
+ const status = async (swiftbar = false, all = false, excludes = []) => {
1688
+ let allProjects;
1689
+ let selfPath;
1690
+ if (all) {
1691
+ const { cnsync, consumers, self } = await discover(void 0, excludes);
1692
+ selfPath = self.path;
1693
+ allProjects = [
1694
+ self,
1695
+ cnsync,
1696
+ ...consumers
1697
+ ];
1698
+ } else {
1699
+ const projectPath = await isInsideProject();
1700
+ if (projectPath) {
1701
+ const { self } = await discoverSources();
1702
+ selfPath = self.path;
1703
+ allProjects = [{
1704
+ name: projectName(projectPath),
1705
+ path: projectPath
1706
+ }];
1707
+ } else {
1708
+ const { cnsync, consumers, self } = await discover(void 0, excludes);
1709
+ selfPath = self.path;
1710
+ allProjects = [
1711
+ self,
1712
+ cnsync,
1713
+ ...consumers
1714
+ ];
1715
+ }
1716
+ }
1717
+ const allIssues = /* @__PURE__ */ new Map();
1718
+ const checks = allProjects.map(async (project) => {
1719
+ const name = projectName(project.path);
1720
+ emitToSocket(createEvent({
1721
+ project: name,
1722
+ status: "start",
1723
+ step: "check"
1724
+ }));
1725
+ const issues = [];
1726
+ const results = await Promise.all([
1727
+ checkGit(project.path),
1728
+ checkDrift(selfPath, project.path),
1729
+ checkRootPkg(project.path),
1730
+ checkConfigs(project.path),
1731
+ checkForbidden(project.path),
1732
+ checkLayouts(project.path),
1733
+ checkPages(project.path),
1734
+ checkShadcnClasses(project.path),
1735
+ checkDepsLatest(project.path),
1736
+ checkNextConfigs(project.path),
1737
+ checkAppTsconfigs(project.path),
1738
+ checkBannedImports(project.path),
1739
+ checkFumadocsBuild(project.path),
1740
+ checkFumadocsGithubUrl(project.path),
1741
+ checkMergeMarkers(project.path),
1742
+ checkConvexSelfHosted(project.path),
1743
+ audit(project.path)
1744
+ ]);
1745
+ for (const r of results) issues.push(...r);
1746
+ issues.push(...await checkCi(project.path));
1747
+ issues.push(...await checkVercel(project.path));
1748
+ allIssues.set(project.path, issues);
1749
+ const hasFails = issues.length > 0;
1750
+ emitToSocket(createEvent({
1751
+ detail: hasFails ? `${issues.length} issues` : void 0,
1752
+ project: name,
1753
+ status: hasFails ? "fail" : "ok",
1754
+ step: "check"
1755
+ }));
1756
+ emitToSocket(createEvent({
1757
+ detail: hasFails ? `${issues.length} issues` : "clean",
1758
+ project: name,
1759
+ status: hasFails ? "fail" : "ok",
1760
+ step: "done"
1761
+ }));
1762
+ });
1763
+ await Promise.all(checks);
1764
+ for (const project of allProjects) spawnBackgroundCheck(project.path);
1765
+ if (swiftbar) console.log(await formatSwiftBar(allIssues));
1766
+ else {
1767
+ for (const [path, issues] of allIssues) {
1768
+ console.log(formatIssues(path, issues));
1769
+ console.log();
1770
+ }
1771
+ if (process.platform === "darwin") await $`open swiftbar://refreshplugin?name=pm4ai`.quiet().nothrow();
1772
+ }
1773
+ };
1774
+ //#endregion
1775
+ export { timeAgo as n, status as t };