@stacksjs/actions 0.70.87 → 0.70.90

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (159) hide show
  1. package/dist/action.js +78 -2
  2. package/dist/actions.js +2 -2
  3. package/dist/add.js +20 -2
  4. package/dist/ai/request-model-access.js +10 -2
  5. package/dist/auth/client.js +44 -3
  6. package/dist/auth/prune.js +35 -2
  7. package/dist/auth/setup.js +152 -36
  8. package/dist/auth/token.js +10 -2
  9. package/dist/blog-admin.d.ts +55 -0
  10. package/dist/blog-admin.js +156 -0
  11. package/dist/blog.d.ts +11 -0
  12. package/dist/blog.js +306 -56
  13. package/dist/build/cli.js +6 -2
  14. package/dist/build/component-libs.js +7 -2
  15. package/dist/build/core.js +55 -2
  16. package/dist/build/desktop.js +5 -2
  17. package/dist/build/docs.js +5 -2
  18. package/dist/build/frontend-static.js +123 -8
  19. package/dist/build/server.js +6 -2
  20. package/dist/build/stacks.js +8 -2
  21. package/dist/build/views.js +11 -2
  22. package/dist/build.js +64 -2
  23. package/dist/bump.js +162 -7
  24. package/dist/cdn/invalidate-cache.js +0 -1
  25. package/dist/changelog.js +27 -3
  26. package/dist/check/ports.js +16 -2
  27. package/dist/clean.js +4 -2
  28. package/dist/commit.js +12 -2
  29. package/dist/copy-types.js +10 -3
  30. package/dist/database/seed.js +26 -2
  31. package/dist/deploy/create-receipt-rule.js +36 -2
  32. package/dist/deploy/index.js +1318 -44
  33. package/dist/dev/api.js +94 -6
  34. package/dist/dev/components.js +7 -2
  35. package/dist/dev/dashboard-globals.js +89 -3
  36. package/dist/dev/dashboard-utils.js +380 -2
  37. package/dist/dev/dashboard.js +435 -8
  38. package/dist/dev/desktop.js +5 -2
  39. package/dist/dev/docs.js +24 -2
  40. package/dist/dev/index.js +29 -2
  41. package/dist/dev/migrate-watcher.js +74 -2
  42. package/dist/dev/system-tray.js +5 -2
  43. package/dist/dev/views.js +147 -2
  44. package/dist/discover-packages.js +32 -2
  45. package/dist/domains/add.js +61 -2
  46. package/dist/domains/purchase.js +66 -2
  47. package/dist/domains/remove.js +24 -2
  48. package/dist/examples.js +34 -2
  49. package/dist/find-projects.js +16 -2
  50. package/dist/fresh.js +17 -2
  51. package/dist/generate/action-types.js +9 -4
  52. package/dist/generate/component-meta.js +9 -3
  53. package/dist/generate/custom-cli-file.js +20 -3
  54. package/dist/generate/env-files.js +46 -6
  55. package/dist/generate/ide-helpers.js +8 -3
  56. package/dist/generate/index.js +158 -2
  57. package/dist/generate/lib-entries.js +16 -3
  58. package/dist/generate/pantry-file.js +46 -2
  59. package/dist/generate/symlink-core.js +2 -2
  60. package/dist/generate/types.js +3 -2
  61. package/dist/generate/vscode-custom-data.js +9 -3
  62. package/dist/generate/web-types.js +0 -1
  63. package/dist/helpers/component-meta.js +295 -2
  64. package/dist/helpers/index.js +1 -2
  65. package/dist/helpers/lib-entries.js +73 -2
  66. package/dist/helpers/package-json.js +45 -14
  67. package/dist/helpers/utils.js +105 -2
  68. package/dist/helpers/vscode-custom-data.js +30 -2
  69. package/dist/index.d.ts +13 -0
  70. package/dist/index.js +50 -2
  71. package/dist/key-generate.js +24 -2
  72. package/dist/lint/fix.js +4 -3
  73. package/dist/lint/index.js +4 -3
  74. package/dist/lint/lint.js +34 -2
  75. package/dist/make-command.js +64 -14
  76. package/dist/make-job.js +41 -16
  77. package/dist/make-policy.js +74 -26
  78. package/dist/make-resource.js +50 -21
  79. package/dist/make.js +328 -2
  80. package/dist/migrate/database.js +16 -2
  81. package/dist/migrate/fresh.js +31 -2
  82. package/dist/orm/base.js +0 -2
  83. package/dist/orm/test.js +0 -1
  84. package/dist/prepublish.js +16 -2
  85. package/dist/queue/clear.js +42 -2
  86. package/dist/queue/dlq-purge.js +28 -2
  87. package/dist/queue/dlq-retry.js +35 -2
  88. package/dist/queue/dlq.js +55 -5
  89. package/dist/queue/failed.js +69 -5
  90. package/dist/queue/flush.js +49 -3
  91. package/dist/queue/inspect.js +154 -8
  92. package/dist/queue/list.js +69 -5
  93. package/dist/queue/monitor.js +94 -6
  94. package/dist/queue/pause.js +28 -2
  95. package/dist/queue/quarantine.js +42 -3
  96. package/dist/queue/resume.js +27 -2
  97. package/dist/queue/retry.js +41 -2
  98. package/dist/queue/schedule-list.js +33 -5
  99. package/dist/queue/schedule.js +12 -2
  100. package/dist/queue/status.js +68 -5
  101. package/dist/queue/table.js +10 -2
  102. package/dist/queue/unquarantine.js +27 -2
  103. package/dist/queue/work.js +57 -2
  104. package/dist/release.js +21 -3
  105. package/dist/route/list.js +78 -3
  106. package/dist/run-action.js +1 -2
  107. package/dist/saas/setup.js +9 -2
  108. package/dist/scaffold-crud.js +164 -40
  109. package/dist/schedule/run.js +7 -2
  110. package/dist/search/flush.js +10 -2
  111. package/dist/search/import.js +10 -2
  112. package/dist/search/settings-list.js +10 -2
  113. package/dist/search/settings.js +9 -2
  114. package/dist/serve/api.js +35 -2
  115. package/dist/setup/index.js +1 -2
  116. package/dist/setup/ssl.js +271 -2
  117. package/dist/stacks.js +265 -2
  118. package/dist/templates.js +301 -2
  119. package/dist/test/feature.js +11 -2
  120. package/dist/test/index.js +11 -2
  121. package/dist/test/ui.js +4 -2
  122. package/dist/test/unit.js +11 -2
  123. package/dist/typecheck.js +4 -2
  124. package/dist/types.js +16 -2
  125. package/dist/upgrade/binary.js +29 -2
  126. package/dist/upgrade/bun.js +0 -1
  127. package/dist/upgrade/dependencies.js +0 -2
  128. package/dist/upgrade/framework-utils.js +193 -2
  129. package/dist/upgrade/framework.js +310 -7
  130. package/dist/upgrade/index.js +17 -2
  131. package/dist/upgrade/packages.js +99 -12
  132. package/dist/upgrade/shell.js +40 -4
  133. package/dist/upgrade.js +38 -2
  134. package/package.json +16 -16
  135. package/dist/chunk-0c8sd1t6.js +0 -2
  136. package/dist/chunk-0g2nzhx9.js +0 -240
  137. package/dist/chunk-0gadhvn7.js +0 -3
  138. package/dist/chunk-0h27aa4r.js +0 -93
  139. package/dist/chunk-3peh8kqs.js +0 -851
  140. package/dist/chunk-62gdfav8.js +0 -3
  141. package/dist/chunk-6bk53y02.js +0 -3
  142. package/dist/chunk-78c5n3s2.js +0 -3
  143. package/dist/chunk-c74qv38e.js +0 -17
  144. package/dist/chunk-cdk2r2jd.js +0 -1
  145. package/dist/chunk-dbz9dp2h.js +0 -3
  146. package/dist/chunk-e142dheq.js +0 -3
  147. package/dist/chunk-ec4gky40.js +0 -3
  148. package/dist/chunk-h3d81wrg.js +0 -3
  149. package/dist/chunk-m3q0x4h0.js +0 -688
  150. package/dist/chunk-mqgbyhph.js +0 -5
  151. package/dist/chunk-n24m8prb.js +0 -3
  152. package/dist/chunk-n5eqwhck.js +0 -7
  153. package/dist/chunk-nsvnqv8y.js +0 -5
  154. package/dist/chunk-nty6mmx4.js +0 -283
  155. package/dist/chunk-s735m9mg.js +0 -1
  156. package/dist/chunk-tpb8jgsk.js +0 -7
  157. package/dist/chunk-tsbk5vne.js +0 -3
  158. package/dist/chunk-xg9xscqz.js +0 -1
  159. package/dist/chunk-y6e448he.js +0 -7
package/dist/dev/api.js CHANGED
@@ -1,6 +1,94 @@
1
- // @bun
2
- import{Lb as K}from"../chunk-h3d81wrg.js";import{existsSync as L}from"fs";import N from"process";import{parseOptions as Q}from"@stacksjs/cli";import{config as T,overridesReady as U}from"@stacksjs/config";import{path as V}from"@stacksjs/path";import{route as X}from"@stacksjs/router";import{generateAutoImportFiles as W,injectGlobalAutoImports as F}from"@stacksjs/server";var x=Q();await U;var $=Number(N.env.PORT_API)||T.ports?.api||3008,R=V.storagePath("framework/auto-imports/models.ts");if(!L(R))await W();await F();try{let z=V.appPath("Lib/typesense-dev.ts");if(L(z)){let B=await import(z);if(typeof B.ensureTypesenseRunning==="function")await B.ensureTypesenseRunning()}}catch(z){let{log:B}=await import("@stacksjs/cli");B.warn(`[api:dev] search engine bootstrap skipped: ${z.message}`)}try{let z=V.appPath("Listener.ts");if(L(z)){let B=await import(z);if(typeof B.handleEvents==="function")await B.handleEvents()}}catch(z){let{log:B}=await import("@stacksjs/cli");B.warn(`[api:dev] failed to bootstrap event listeners \u2014 dispatched events will be ignored: ${z.message}`)}try{let{startModelMigrationWatcher:z}=await import("./migrate-watcher.js");z()}catch(z){let{log:B}=await import("@stacksjs/cli");B.debug(`[api:dev] auto-migrate watcher not started: ${z.message}`)}function _(){let z=V.frameworkPath("defaults/app/Middleware/Cors.ts");if(L(z))return z;try{return`${Bun.resolveSync("@stacksjs/defaults/package.json",N.cwd()).replace(/package\.json$/,"")}app/Middleware/Cors.ts`}catch{return z}}var w=await import(_()),A=w.default;X.use(A.toRouterHandler());var E=/^[a-zA-Z0-9._-]{8,128}$/;X.use(async(z,B)=>{let Y=z?.headers?.get?.("x-request-id")||z?.headers?.get?.("X-Request-ID")||"",Z=typeof Y==="string"&&E.test(Y)?Y:crypto.randomUUID();z._requestId=Z,z._startNs=N.hrtime.bigint();let H=await B();if(H&&H.status===404&&(H.headers.get("content-type")||"").includes("json"))try{let J=await H.clone().json();if(J&&(J.message==="Not Found"||J.error==="Not Found")){let G=new URL(z.url),O={...J,path:G.pathname,method:z.method,request_id:Z},C=new Headers(H.headers);return C.set("X-Request-ID",Z),new Response(JSON.stringify(O),{status:404,headers:C})}}catch{}return H});await X.importRoutes();try{await X.serve({port:$,hostname:"127.0.0.1"}),console.log(`
3
- \u279C API server ready: http://localhost:${$}
4
- `)}catch(z){if((z?.code||z?.errno)==="EADDRINUSE"||String(z?.message||"").includes("EADDRINUSE"))console.error(`
5
- [dev/api] Port ${$} is already in use. Kill the other process and re-run \`./buddy dev\`, or set PORT_API to another port.
6
- `),N.exit(1);throw z}
1
+ import { existsSync } from "node:fs";
2
+ import process from "node:process";
3
+ import { parseOptions } from "@stacksjs/cli";
4
+ import { config, overridesReady } from "@stacksjs/config";
5
+ import { path } from "@stacksjs/path";
6
+ import { route } from "@stacksjs/router";
7
+ import { generateAutoImportFiles, injectGlobalAutoImports } from "@stacksjs/server";
8
+ const _options = parseOptions();
9
+ await overridesReady;
10
+ const port = Number(process.env.PORT_API) || config.ports?.api || 3008, modelsIndex = path.storagePath("framework/auto-imports/models.ts");
11
+ if (!existsSync(modelsIndex))
12
+ await generateAutoImportFiles();
13
+ await injectGlobalAutoImports();
14
+ try {
15
+ const typesenseDev = path.appPath("Lib/typesense-dev.ts");
16
+ if (existsSync(typesenseDev)) {
17
+ const mod = await import(typesenseDev);
18
+ if (typeof mod.ensureTypesenseRunning === "function")
19
+ await mod.ensureTypesenseRunning();
20
+ }
21
+ } catch (err) {
22
+ const { log } = await import("@stacksjs/cli");
23
+ log.warn(`[api:dev] search engine bootstrap skipped: ${err.message}`);
24
+ }
25
+ try {
26
+ const listenerPath = path.appPath("Listener.ts");
27
+ if (existsSync(listenerPath)) {
28
+ const listener = await import(listenerPath);
29
+ if (typeof listener.handleEvents === "function")
30
+ await listener.handleEvents();
31
+ }
32
+ } catch (err) {
33
+ const { log } = await import("@stacksjs/cli");
34
+ log.warn(`[api:dev] failed to bootstrap event listeners \u2014 dispatched events will be ignored: ${err.message}`);
35
+ }
36
+ try {
37
+ const { startModelMigrationWatcher } = await import("./migrate-watcher");
38
+ startModelMigrationWatcher();
39
+ } catch (err) {
40
+ const { log } = await import("@stacksjs/cli");
41
+ log.debug(`[api:dev] auto-migrate watcher not started: ${err.message}`);
42
+ }
43
+ function resolveDefaultsCorsPath() {
44
+ const vendored = path.frameworkPath("defaults/app/Middleware/Cors.ts");
45
+ if (existsSync(vendored))
46
+ return vendored;
47
+ try {
48
+ return `${Bun.resolveSync("@stacksjs/defaults/package.json", process.cwd()).replace(/package\.json$/, "")}app/Middleware/Cors.ts`;
49
+ } catch {
50
+ return vendored;
51
+ }
52
+ }
53
+ const corsMod = await import(resolveDefaultsCorsPath()), corsMiddleware = corsMod.default;
54
+ route.use(corsMiddleware.toRouterHandler());
55
+ const SAFE_REQUEST_ID = /^[a-zA-Z0-9._-]{8,128}$/;
56
+ route.use(async (request, next) => {
57
+ const inbound = request?.headers?.get?.("x-request-id") || request?.headers?.get?.("X-Request-ID") || "", id = typeof inbound === "string" && SAFE_REQUEST_ID.test(inbound) ? inbound : crypto.randomUUID();
58
+ request._requestId = id;
59
+ request._startNs = process.hrtime.bigint();
60
+ const response = await next();
61
+ if (response && response.status === 404 && (response.headers.get("content-type") || "").includes("json"))
62
+ try {
63
+ const body = await response.clone().json();
64
+ if (body && (body.message === "Not Found" || body.error === "Not Found")) {
65
+ const url = new URL(request.url), enriched = {
66
+ ...body,
67
+ path: url.pathname,
68
+ method: request.method,
69
+ request_id: id
70
+ }, headers = new Headers(response.headers);
71
+ headers.set("X-Request-ID", id);
72
+ return new Response(JSON.stringify(enriched), { status: 404, headers });
73
+ }
74
+ } catch {}
75
+ return response;
76
+ });
77
+ await route.importRoutes();
78
+ try {
79
+ await route.serve({
80
+ port,
81
+ hostname: "127.0.0.1"
82
+ });
83
+ console.log(`
84
+ \u279C API server ready: http://localhost:${port}
85
+ `);
86
+ } catch (err) {
87
+ if ((err?.code || err?.errno) === "EADDRINUSE" || String(err?.message || "").includes("EADDRINUSE")) {
88
+ console.error(`
89
+ [dev/api] Port ${port} is already in use. Kill the other process and re-run \`./buddy dev\`, or set PORT_API to another port.
90
+ `);
91
+ process.exit(1);
92
+ }
93
+ throw err;
94
+ }
@@ -1,2 +1,7 @@
1
- // @bun
2
- import{parseOptions as f,runCommand as q}from"@stacksjs/cli";import{libsPath as w}from"@stacksjs/path";var x=f();await q("bun run dev",{...x,cwd:w("components/vue")});
1
+ import { parseOptions, runCommand } from "@stacksjs/cli";
2
+ import { libsPath } from "@stacksjs/path";
3
+ const options = parseOptions();
4
+ await runCommand("bun run dev", {
5
+ ...options,
6
+ cwd: libsPath("components/vue")
7
+ });
@@ -1,3 +1,89 @@
1
- // @bun
2
- import"../chunk-h3d81wrg.js";import{existsSync as $,readdirSync as U,statSync as k}from"fs";import{join as V}from"path";import w from"process";import{projectPath as W,storagePath as D}from"@stacksjs/path";var H=[D("framework/defaults/resources/functions/dashboard"),W("resources/functions"),W("app/Helpers")];async function F(q={}){let B=globalThis,C=[],G=0;for(let J of H){if(!$(J))continue;let K;try{K=U(J)}catch{continue}for(let A of K){let z=V(J,A),M;try{M=k(z)}catch{continue}if(M.isDirectory()){let O;try{O=U(z)}catch{continue}for(let Q of O){if(!X(Q))continue;let Z=V(z,Q),T=await Y(Z,B,q.verbose);G+=T.added,C.push(...T.collisions)}continue}if(!X(A))continue;let N=await Y(z,B,q.verbose);G+=N.added,C.push(...N.collisions)}}if(q.verbose)w.stdout.write(`[dashboard] hoisted ${G} helper globals`+(C.length>0?` (skipped ${C.length} collisions: ${C.slice(0,5).join(", ")}\u2026)`:"")+`
3
- `)}function X(q){if(!/\.(?:ts|tsx|js|mjs)$/.test(q))return!1;if(q.startsWith("_"))return!1;if(q.endsWith(".test.ts")||q.endsWith(".spec.ts"))return!1;if(q.endsWith(".d.ts"))return!1;return!0}async function Y(q,B,C){let G;try{G=await import(q)}catch{return{added:0,collisions:[]}}let J=[],K=0;for(let[A,z]of Object.entries(G)){if(A==="default"||A.startsWith("_"))continue;if(typeof z==="function"||z&&typeof z==="object"){if(B[A]===void 0)B[A]=z,K++;else if(B[A]!==z)J.push(A)}}return{added:K,collisions:J}}export{F as hoistDashboardGlobals};
1
+ import { existsSync, readdirSync, statSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import process from "node:process";
4
+ import { projectPath, storagePath } from "@stacksjs/path";
5
+ const HELPER_DIRS = [
6
+ storagePath("framework/defaults/resources/functions/dashboard"),
7
+ projectPath("resources/functions"),
8
+ projectPath("app/Helpers")
9
+ ];
10
+ export async function hoistDashboardGlobals(opts = {}) {
11
+ const g = globalThis, collisions = [];
12
+ let hoisted = 0;
13
+ for (const dir of HELPER_DIRS) {
14
+ if (!existsSync(dir))
15
+ continue;
16
+ let entries;
17
+ try {
18
+ entries = readdirSync(dir);
19
+ } catch {
20
+ continue;
21
+ }
22
+ for (const entry of entries) {
23
+ const full = join(dir, entry);
24
+ let s;
25
+ try {
26
+ s = statSync(full);
27
+ } catch {
28
+ continue;
29
+ }
30
+ if (s.isDirectory()) {
31
+ let subEntries;
32
+ try {
33
+ subEntries = readdirSync(full);
34
+ } catch {
35
+ continue;
36
+ }
37
+ for (const sub of subEntries) {
38
+ if (!isImportable(sub))
39
+ continue;
40
+ const subFull = join(full, sub), r = await hoistFile(subFull, g, opts.verbose);
41
+ hoisted += r.added;
42
+ collisions.push(...r.collisions);
43
+ }
44
+ continue;
45
+ }
46
+ if (!isImportable(entry))
47
+ continue;
48
+ const r = await hoistFile(full, g, opts.verbose);
49
+ hoisted += r.added;
50
+ collisions.push(...r.collisions);
51
+ }
52
+ }
53
+ if (opts.verbose)
54
+ process.stdout.write(`[dashboard] hoisted ${hoisted} helper globals` + (collisions.length > 0 ? ` (skipped ${collisions.length} collisions: ${collisions.slice(0, 5).join(", ")}\u2026)` : "") + `
55
+ `);
56
+ }
57
+ function isImportable(name) {
58
+ if (!/\.(?:ts|tsx|js|mjs)$/.test(name))
59
+ return !1;
60
+ if (name.startsWith("_"))
61
+ return !1;
62
+ if (name.endsWith(".test.ts") || name.endsWith(".spec.ts"))
63
+ return !1;
64
+ if (name.endsWith(".d.ts"))
65
+ return !1;
66
+ return !0;
67
+ }
68
+ async function hoistFile(file, g, _verbose) {
69
+ let mod;
70
+ try {
71
+ mod = await import(file);
72
+ } catch {
73
+ return { added: 0, collisions: [] };
74
+ }
75
+ const collisions = [];
76
+ let added = 0;
77
+ for (const [name, value] of Object.entries(mod)) {
78
+ if (name === "default" || name.startsWith("_"))
79
+ continue;
80
+ if (typeof value === "function" || value && typeof value === "object") {
81
+ if (g[name] === void 0) {
82
+ g[name] = value;
83
+ added++;
84
+ } else if (g[name] !== value)
85
+ collisions.push(name);
86
+ }
87
+ }
88
+ return { added, collisions };
89
+ }
@@ -1,2 +1,380 @@
1
- // @bun
2
- import{$a as e,Xa as a,Ya as b,Za as c,_a as d,ab as f,bb as g,cb as h,db as i,eb as j,fb as k,gb as l,hb as m}from"../chunk-ec4gky40.js";import"../chunk-h3d81wrg.js";export{k as waitForServer,f as scanModelsDir,d as modelNameToId,i as isPortAvailable,a as iconMap,e as getModelIcon,j as findAvailablePort,b as excludeModels,g as discoverModels,c as dedicatedPages,l as buildSidebarConfig,h as buildManifest,m as buildDashboardUrl};
1
+ import { Glob } from "bun";
2
+ import path from "node:path";
3
+ export const iconMap = {
4
+ user: "person.fill",
5
+ team: "person.2.fill",
6
+ subscriber: "envelope.open.fill",
7
+ subscriberemail: "envelope.fill",
8
+ post: "doc.text.fill",
9
+ page: "doc.fill",
10
+ product: "cube.box.fill",
11
+ productvariant: "cube.fill",
12
+ productunit: "scalemass.fill",
13
+ order: "list.clipboard.fill",
14
+ orderitem: "list.bullet.clipboard.fill",
15
+ customer: "person.crop.circle.fill",
16
+ payment: "creditcard.fill",
17
+ paymentmethod: "creditcard.and.123",
18
+ paymentproduct: "creditcard.fill",
19
+ paymenttransaction: "creditcard.fill",
20
+ category: "tag.fill",
21
+ tag: "tag",
22
+ comment: "bubble.left.fill",
23
+ author: "person.text.rectangle.fill",
24
+ notification: "bell.fill",
25
+ job: "briefcase.fill",
26
+ log: "list.bullet.rectangle.portrait.fill",
27
+ campaign: "megaphone.fill",
28
+ review: "star.fill",
29
+ coupon: "ticket.fill",
30
+ giftcard: "giftcard.fill",
31
+ cart: "cart.fill",
32
+ cartitem: "cart.badge.plus",
33
+ taxrate: "percent",
34
+ transaction: "arrow.left.arrow.right.circle.fill",
35
+ subscription: "repeat.circle.fill",
36
+ emaillist: "envelope.badge.person.crop",
37
+ socialpost: "bubble.left.and.bubble.right.fill",
38
+ manufacturer: "building.2.fill",
39
+ shippingmethod: "shippingbox.fill",
40
+ shippingzone: "map.fill",
41
+ shippingrate: "dollarsign.circle.fill",
42
+ deliveryroute: "map.fill",
43
+ driver: "car.fill",
44
+ digitaldelivery: "arrow.down.doc.fill",
45
+ licensekey: "key.fill",
46
+ loyaltypoint: "star.circle.fill",
47
+ loyaltyreward: "gift.fill",
48
+ printdevice: "printer.fill",
49
+ receipt: "doc.text.fill",
50
+ waitlistproduct: "clock.fill",
51
+ waitlistrestaurant: "clock.badge.checkmark.fill",
52
+ websocket: "antenna.radiowaves.left.and.right",
53
+ default: "tablecells.fill"
54
+ }, excludeModels = new Set(["activity", "request", "error", "failedjob"]), dedicatedPages = new Set(["user", "team", "subscriber", "post", "page", "product", "order", "customer", "payment", "coupon", "category", "tag", "comment", "author", "notification", "job", "campaign", "review", "gift-card"]);
55
+ const ROOT_MODEL_CATEGORY = {
56
+ user: "data",
57
+ team: "data",
58
+ subscriber: "data",
59
+ activity: "data",
60
+ campaign: "marketing",
61
+ emaillist: "marketing",
62
+ socialpost: "marketing",
63
+ subscriberemail: "marketing",
64
+ comment: "content",
65
+ tag: "content",
66
+ paymentmethod: "commerce",
67
+ paymentproduct: "commerce",
68
+ paymenttransaction: "commerce",
69
+ subscription: "commerce",
70
+ deployment: "system",
71
+ error: "system",
72
+ failedjob: "system",
73
+ job: "system",
74
+ log: "system",
75
+ notification: "system",
76
+ release: "system",
77
+ request: "system"
78
+ };
79
+ export function modelNameToId(name) {
80
+ return name.replace(/([A-Z])/g, "-$1").toLowerCase().replace(/^-/, "");
81
+ }
82
+ export function getModelIcon(name) {
83
+ return iconMap[name.toLowerCase()] || iconMap.default || "database";
84
+ }
85
+ function categorizeModel(relativePath, modelId, defaultCategory) {
86
+ if (defaultCategory)
87
+ return defaultCategory;
88
+ const firstSeg = relativePath.includes("/") ? (relativePath.split("/")[0] ?? "").toLowerCase() : "";
89
+ if (firstSeg === "commerce")
90
+ return "commerce";
91
+ if (firstSeg === "content")
92
+ return "content";
93
+ if (firstSeg === "realtime")
94
+ return "system";
95
+ return ROOT_MODEL_CATEGORY[modelId.replace(/-/g, "")] ?? "data";
96
+ }
97
+ async function readModelDashboardConfig(absolutePath) {
98
+ try {
99
+ const mod = await import(absolutePath);
100
+ return (mod.default ?? mod).dashboard;
101
+ } catch {
102
+ return;
103
+ }
104
+ }
105
+ export async function scanModelsDir(dir, seenNames, models, defaultCategory) {
106
+ try {
107
+ const glob = new Glob("**/*.ts");
108
+ for await (const file of glob.scan({ cwd: dir })) {
109
+ if (file.includes("README") || file.includes(".d.ts"))
110
+ continue;
111
+ const name = file.replace(/\.ts$/, "").split("/").pop() || "", nameLower = name.toLowerCase();
112
+ if (name && !seenNames.has(nameLower) && !excludeModels.has(nameLower)) {
113
+ seenNames.add(nameLower);
114
+ const id = modelNameToId(name), absolutePath = path.join(dir, file), dashboard = await readModelDashboardConfig(absolutePath);
115
+ models.push({
116
+ name,
117
+ icon: dashboard?.icon ?? getModelIcon(name),
118
+ id,
119
+ category: categorizeModel(file, id, defaultCategory),
120
+ dashboard
121
+ });
122
+ }
123
+ }
124
+ } catch {}
125
+ }
126
+ export async function discoverModels(userModelsPath, defaultModelsPath) {
127
+ const models = [], seenNames = new Set;
128
+ await Promise.all([
129
+ scanModelsDir(userModelsPath, seenNames, models, "userland"),
130
+ scanModelsDir(defaultModelsPath, seenNames, models)
131
+ ]);
132
+ return models.sort((a, b) => a.name.localeCompare(b.name));
133
+ }
134
+ export function buildManifest(models) {
135
+ return models.map((m) => {
136
+ const row = {
137
+ id: m.id,
138
+ name: m.name,
139
+ icon: m.icon,
140
+ hasDedicatedPage: dedicatedPages.has(m.id),
141
+ category: m.category ?? "data"
142
+ };
143
+ if (m.dashboard)
144
+ row.dashboard = m.dashboard;
145
+ return row;
146
+ });
147
+ }
148
+ export async function isPortAvailable(port) {
149
+ try {
150
+ Bun.serve({ port, fetch: () => new Response }).stop(!0);
151
+ return !0;
152
+ } catch {
153
+ return !1;
154
+ }
155
+ }
156
+ export async function findAvailablePort(preferred, maxAttempts = 20) {
157
+ for (let i = 0;i < maxAttempts; i++) {
158
+ const port = preferred + i;
159
+ if (await isPortAvailable(port))
160
+ return port;
161
+ }
162
+ return preferred;
163
+ }
164
+ export async function waitForServer(port, maxWait = 500) {
165
+ const start = Date.now();
166
+ while (Date.now() - start < maxWait) {
167
+ try {
168
+ const res = await fetch(`http://localhost:${port}/`, { method: "HEAD" });
169
+ if (res.ok || res.status === 404)
170
+ return !0;
171
+ } catch {}
172
+ await new Promise((resolve) => setTimeout(resolve, 25));
173
+ }
174
+ return !1;
175
+ }
176
+ function effectiveCategory(m) {
177
+ if (m.dashboard?.enabled === !1)
178
+ return;
179
+ if (m.dashboard?.section)
180
+ return m.dashboard.section;
181
+ return m.category ?? "data";
182
+ }
183
+ function categoryRows(baseRoute, models, category, dedicated) {
184
+ return models.filter((m) => effectiveCategory(m) === category).filter((m) => !dedicatedPages.has(m.id) && !dedicated.has(m.id)).map((m) => ({
185
+ id: `model-${m.id}`,
186
+ label: m.dashboard?.label ?? m.name,
187
+ icon: m.icon,
188
+ url: `${baseRoute}/models/${m.id}`,
189
+ ...m.dashboard?.roles && m.dashboard.roles.length > 0 ? { roles: m.dashboard.roles } : {}
190
+ }));
191
+ }
192
+ export function buildSidebarConfig(baseRoute, discoveredModels, toggles = {}) {
193
+ const enabled = {
194
+ library: toggles.library ?? !0,
195
+ content: toggles.content ?? !0,
196
+ commerce: toggles.commerce ?? !0,
197
+ marketing: toggles.marketing ?? !0,
198
+ analytics: toggles.analytics ?? !0,
199
+ management: toggles.management ?? !0,
200
+ utilities: toggles.utilities ?? !0
201
+ }, dataRow = {
202
+ dashboard: toggles.data?.dashboard ?? !0,
203
+ activity: toggles.data?.activity ?? !0,
204
+ users: toggles.data?.users ?? !0,
205
+ teams: toggles.data?.teams ?? !0,
206
+ subscribers: toggles.data?.subscribers ?? !0,
207
+ allModels: toggles.data?.allModels ?? !0
208
+ }, sections = [], homeItems = [
209
+ { id: "home", label: "Dashboard", icon: "house.fill", url: `${baseRoute}/` },
210
+ { id: "dependencies", label: "Dependencies", icon: "shippingbox.fill", url: `${baseRoute}/dependencies` },
211
+ { id: "health", label: "Health", icon: "heart.text.square.fill", url: `${baseRoute}/health` }
212
+ ];
213
+ if (toggles.ci)
214
+ homeItems.push({ id: "ci", label: "CI", icon: "checkmark.seal.fill", url: `${baseRoute}/ci` });
215
+ sections.push({
216
+ id: "home",
217
+ title: "Home",
218
+ items: homeItems
219
+ });
220
+ if (enabled.library)
221
+ sections.push({
222
+ id: "library",
223
+ title: "Library",
224
+ items: [
225
+ { id: "functions", label: "Functions", icon: "function", url: `${baseRoute}/functions` },
226
+ { id: "packages", label: "Packages", icon: "shippingbox.fill", url: `${baseRoute}/packages` }
227
+ ]
228
+ });
229
+ if (enabled.content)
230
+ sections.push({
231
+ id: "content",
232
+ title: "Content",
233
+ items: [
234
+ { id: "content-dashboard", label: "Dashboard", icon: "gauge.with.dots.needle.33percent", url: `${baseRoute}/content/dashboard` },
235
+ { id: "files", label: "Files", icon: "folder.fill", url: `${baseRoute}/content/files` },
236
+ { id: "pages", label: "Pages", icon: "doc.fill", url: `${baseRoute}/content/pages` },
237
+ { id: "posts", label: "Posts", icon: "text.bubble.fill", url: `${baseRoute}/content/posts` },
238
+ { id: "categories", label: "Categories", icon: "tag.fill", url: `${baseRoute}/content/categories` },
239
+ { id: "tags", label: "Tags", icon: "tag", url: `${baseRoute}/content/tags` },
240
+ { id: "comments", label: "Comments", icon: "bubble.left.fill", url: `${baseRoute}/content/comments` },
241
+ { id: "authors", label: "Authors", icon: "person.text.rectangle.fill", url: `${baseRoute}/content/authors` },
242
+ { id: "seo", label: "SEO", icon: "magnifyingglass", url: `${baseRoute}/content/seo` },
243
+ ...categoryRows(baseRoute, discoveredModels, "content", new Set([
244
+ "page",
245
+ "post",
246
+ "category",
247
+ "tag",
248
+ "comment",
249
+ "author"
250
+ ]))
251
+ ]
252
+ });
253
+ sections.push({
254
+ id: "app",
255
+ title: "App",
256
+ items: [
257
+ { id: "deployments", label: "Deployments", icon: "rocket.fill", url: `${baseRoute}/deployments` },
258
+ { id: "requests", label: "Requests", icon: "arrow.left.arrow.right", url: `${baseRoute}/requests` },
259
+ { id: "realtime", label: "Realtime", icon: "link", url: `${baseRoute}/realtime` },
260
+ { id: "actions", label: "Actions", icon: "bolt.fill", url: `${baseRoute}/actions` },
261
+ { id: "commands", label: "Commands", icon: "terminal.fill", url: `${baseRoute}/commands` },
262
+ { id: "queue", label: "Queue", icon: "list.bullet.rectangle.fill", url: `${baseRoute}/queue` },
263
+ { id: "jobs", label: "Jobs", icon: "briefcase.fill", url: `${baseRoute}/jobs` },
264
+ { id: "inbox", label: "Inbox", icon: "tray.full.fill", url: `${baseRoute}/inbox` },
265
+ { id: "queries", label: "Queries", icon: "magnifyingglass.circle.fill", url: `${baseRoute}/queries` },
266
+ { id: "notifications", label: "Notifications", icon: "bell.fill", url: `${baseRoute}/notifications/dashboard` },
267
+ ...categoryRows(baseRoute, discoveredModels, "system", new Set)
268
+ ]
269
+ });
270
+ const dataItems = [];
271
+ if (dataRow.dashboard)
272
+ dataItems.push({ id: "data-dashboard", label: "Dashboard", icon: "gauge.with.dots.needle.33percent", url: `${baseRoute}/data/dashboard` });
273
+ if (dataRow.activity)
274
+ dataItems.push({ id: "activity", label: "Activity", icon: "waveform.path.ecg", url: `${baseRoute}/data/activity` });
275
+ if (dataRow.users)
276
+ dataItems.push({ id: "users", label: "Users", icon: "person.crop.circle.fill", url: `${baseRoute}/data/users` });
277
+ if (dataRow.teams)
278
+ dataItems.push({ id: "teams", label: "Teams", icon: "person.3.fill", url: `${baseRoute}/data/teams` });
279
+ if (dataRow.subscribers)
280
+ dataItems.push({ id: "subscribers", label: "Subscribers", icon: "envelope.fill", url: `${baseRoute}/data/subscribers` });
281
+ if (dataRow.allModels)
282
+ dataItems.push({ id: "data-models", label: "All Models", icon: "square.grid.2x2.fill", url: `${baseRoute}/models` });
283
+ const dynamicRows = discoveredModels.filter((m) => {
284
+ const eff = effectiveCategory(m);
285
+ return eff === "userland" || eff === "data";
286
+ }).filter((m) => !dedicatedPages.has(m.id)).map((model) => ({
287
+ id: `model-${model.id}`,
288
+ label: model.dashboard?.label ?? model.name,
289
+ icon: model.icon,
290
+ url: `${baseRoute}/models/${model.id}`,
291
+ ...model.dashboard?.roles && model.dashboard.roles.length > 0 ? { roles: model.dashboard.roles } : {}
292
+ }));
293
+ sections.push({
294
+ id: "data",
295
+ title: "Data",
296
+ items: [...dataItems, ...dynamicRows]
297
+ });
298
+ if (enabled.commerce)
299
+ sections.push({
300
+ id: "commerce",
301
+ title: "Commerce",
302
+ items: [
303
+ { id: "commerce-dashboard", label: "Dashboard", icon: "gauge.with.dots.needle.33percent", url: `${baseRoute}/commerce/dashboard` },
304
+ { id: "customers", label: "Customers", icon: "person.crop.circle.fill", url: `${baseRoute}/commerce/customers` },
305
+ { id: "orders", label: "Orders", icon: "list.clipboard.fill", url: `${baseRoute}/commerce/orders` },
306
+ { id: "products", label: "Products", icon: "cube.box.fill", url: `${baseRoute}/commerce/products` },
307
+ { id: "coupons", label: "Coupons", icon: "ticket.fill", url: `${baseRoute}/commerce/coupons` },
308
+ { id: "gift-cards", label: "Gift Cards", icon: "giftcard.fill", url: `${baseRoute}/commerce/gift-cards` },
309
+ { id: "payments", label: "Payments", icon: "creditcard.fill", url: `${baseRoute}/commerce/payments` },
310
+ { id: "delivery", label: "Delivery", icon: "truck.box.fill", url: `${baseRoute}/commerce/delivery` },
311
+ { id: "taxes", label: "Taxes", icon: "percent", url: `${baseRoute}/commerce/taxes` },
312
+ ...categoryRows(baseRoute, discoveredModels, "commerce", new Set([
313
+ "customer",
314
+ "order",
315
+ "product",
316
+ "coupon",
317
+ "gift-card",
318
+ "payment",
319
+ "review",
320
+ "category"
321
+ ]))
322
+ ]
323
+ });
324
+ if (enabled.marketing)
325
+ sections.push({
326
+ id: "marketing",
327
+ title: "Marketing",
328
+ items: [
329
+ { id: "lists", label: "Lists", icon: "list.bullet", url: `${baseRoute}/marketing/lists` },
330
+ { id: "social-posts", label: "Social Posts", icon: "clock.fill", url: `${baseRoute}/marketing/social-posts` },
331
+ { id: "campaigns", label: "Campaigns", icon: "megaphone.fill", url: `${baseRoute}/marketing/campaigns` },
332
+ { id: "marketing-reviews", label: "Reviews", icon: "star.fill", url: `${baseRoute}/marketing/reviews` },
333
+ ...categoryRows(baseRoute, discoveredModels, "marketing", new Set(["campaign"]))
334
+ ]
335
+ });
336
+ if (enabled.analytics)
337
+ sections.push({
338
+ id: "analytics",
339
+ title: "Analytics",
340
+ items: [
341
+ { id: "analytics-web", label: "Web", icon: "globe", url: `${baseRoute}/analytics/web` },
342
+ { id: "analytics-blog", label: "Blog", icon: "doc.text.fill", url: `${baseRoute}/analytics/blog` },
343
+ { id: "analytics-commerce", label: "Commerce", icon: "cart.fill", url: `${baseRoute}/analytics/commerce/web` },
344
+ { id: "analytics-marketing", label: "Marketing", icon: "megaphone.fill", url: `${baseRoute}/analytics/marketing` }
345
+ ]
346
+ });
347
+ if (enabled.management)
348
+ sections.push({
349
+ id: "management",
350
+ title: "Management",
351
+ items: [
352
+ { id: "cloud", label: "Cloud", icon: "cloud.fill", url: `${baseRoute}/cloud` },
353
+ { id: "servers", label: "Servers", icon: "server.rack", url: `${baseRoute}/servers` },
354
+ { id: "serverless", label: "Serverless", icon: "bolt.horizontal.circle.fill", url: `${baseRoute}/serverless` },
355
+ { id: "dns", label: "DNS", icon: "network", url: `${baseRoute}/dns` },
356
+ { id: "mailboxes", label: "Mailboxes", icon: "tray.full.fill", url: `${baseRoute}/mailboxes` },
357
+ { id: "logs", label: "Logs", icon: "list.bullet.rectangle.portrait.fill", url: `${baseRoute}/logs` },
358
+ { id: "kanban", label: "Kanban", icon: "rectangle.stack.fill", url: `${baseRoute}/kanban`, roles: ["admin", "dev"] }
359
+ ]
360
+ });
361
+ if (enabled.utilities)
362
+ sections.push({
363
+ id: "utilities",
364
+ title: "Utilities",
365
+ items: [
366
+ { id: "buddy", label: "AI Buddy", icon: "bubble.left.and.bubble.right.fill", url: `${baseRoute}/buddy` },
367
+ { id: "environment", label: "Environment", icon: "key.fill", url: `${baseRoute}/environment` },
368
+ { id: "access-tokens", label: "Access Tokens", icon: "key.horizontal.fill", url: `${baseRoute}/access-tokens` },
369
+ { id: "settings", label: "Settings", icon: "gear", url: `${baseRoute}/settings/billing` }
370
+ ]
371
+ });
372
+ return {
373
+ sections,
374
+ minWidth: 200,
375
+ maxWidth: 280
376
+ };
377
+ }
378
+ export function buildDashboardUrl(port) {
379
+ return `http://localhost:${port}/?native-sidebar=1`;
380
+ }