@stacksjs/actions 0.70.88 → 0.70.91

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 (261) hide show
  1. package/dist/action.d.ts +149 -0
  2. package/dist/action.js +78 -0
  3. package/dist/actions.d.ts +2 -0
  4. package/dist/actions.js +2 -0
  5. package/dist/add.d.ts +6 -0
  6. package/dist/add.js +20 -0
  7. package/dist/ai/request-model-access.d.ts +1 -0
  8. package/dist/ai/request-model-access.js +10 -0
  9. package/dist/auth/client.d.ts +1 -0
  10. package/dist/auth/client.js +44 -0
  11. package/dist/auth/prune.d.ts +1 -0
  12. package/dist/auth/prune.js +35 -0
  13. package/dist/auth/setup.d.ts +1 -0
  14. package/dist/auth/setup.js +322 -0
  15. package/dist/auth/token.d.ts +1 -0
  16. package/dist/auth/token.js +10 -0
  17. package/dist/blog-admin.d.ts +55 -0
  18. package/dist/blog-admin.js +156 -0
  19. package/dist/blog.d.ts +30 -0
  20. package/dist/blog.js +398 -0
  21. package/dist/build/cli.d.ts +1 -0
  22. package/dist/build/cli.js +6 -0
  23. package/dist/build/component-libs.d.ts +1 -0
  24. package/dist/build/component-libs.js +7 -0
  25. package/dist/build/core.d.ts +1 -0
  26. package/dist/build/core.js +55 -0
  27. package/dist/build/desktop.d.ts +1 -0
  28. package/dist/build/desktop.js +5 -0
  29. package/dist/build/docs.d.ts +1 -0
  30. package/dist/build/docs.js +5 -0
  31. package/dist/build/frontend-static.d.ts +16 -0
  32. package/dist/build/frontend-static.js +124 -0
  33. package/dist/build/server.d.ts +1 -0
  34. package/dist/build/server.js +6 -0
  35. package/dist/build/stacks.d.ts +1 -0
  36. package/dist/build/stacks.js +8 -0
  37. package/dist/build/views.d.ts +1 -0
  38. package/dist/build/views.js +11 -0
  39. package/dist/build.d.ts +9 -0
  40. package/dist/build.js +64 -0
  41. package/dist/bump.d.ts +1 -0
  42. package/dist/bump.js +163 -0
  43. package/dist/cdn/invalidate-cache.d.ts +1 -0
  44. package/dist/cdn/invalidate-cache.js +0 -0
  45. package/dist/changelog.d.ts +9 -0
  46. package/dist/changelog.js +27 -0
  47. package/dist/check/ports.d.ts +1 -0
  48. package/dist/check/ports.js +16 -0
  49. package/dist/clean.d.ts +1 -0
  50. package/dist/clean.js +4 -0
  51. package/dist/commit.d.ts +3 -0
  52. package/dist/commit.js +12 -0
  53. package/dist/copy-types.d.ts +1 -0
  54. package/dist/copy-types.js +10 -0
  55. package/dist/database/seed.d.ts +1 -0
  56. package/dist/database/seed.js +26 -0
  57. package/dist/deploy/create-receipt-rule.d.ts +1 -0
  58. package/dist/deploy/create-receipt-rule.js +36 -0
  59. package/dist/deploy/index.d.ts +1 -0
  60. package/dist/deploy/index.js +1574 -0
  61. package/dist/dev/api.d.ts +1 -0
  62. package/dist/dev/api.js +94 -0
  63. package/dist/dev/components.d.ts +1 -0
  64. package/dist/dev/components.js +7 -0
  65. package/dist/dev/dashboard-globals.d.ts +4 -0
  66. package/dist/dev/dashboard-globals.js +89 -0
  67. package/dist/dev/dashboard-utils.d.ts +150 -0
  68. package/dist/dev/dashboard-utils.js +380 -0
  69. package/dist/dev/dashboard.d.ts +22 -0
  70. package/dist/dev/dashboard.js +435 -0
  71. package/dist/dev/desktop.d.ts +1 -0
  72. package/dist/dev/desktop.js +5 -0
  73. package/dist/dev/docs.d.ts +1 -0
  74. package/dist/dev/docs.js +24 -0
  75. package/dist/dev/index.d.ts +10 -0
  76. package/dist/dev/index.js +29 -0
  77. package/dist/dev/migrate-watcher.d.ts +1 -0
  78. package/dist/dev/migrate-watcher.js +74 -0
  79. package/dist/dev/system-tray.d.ts +1 -0
  80. package/dist/dev/system-tray.js +5 -0
  81. package/dist/dev/views.d.ts +1 -0
  82. package/dist/dev/views.js +147 -0
  83. package/dist/discover-packages.d.ts +18 -0
  84. package/dist/discover-packages.js +32 -0
  85. package/dist/domains/add.d.ts +1 -0
  86. package/dist/domains/add.js +61 -0
  87. package/dist/domains/purchase.d.ts +1 -0
  88. package/dist/domains/purchase.js +66 -0
  89. package/dist/domains/remove.d.ts +1 -0
  90. package/dist/domains/remove.js +24 -0
  91. package/dist/examples.d.ts +5 -0
  92. package/dist/examples.js +34 -0
  93. package/dist/find-projects.d.ts +1 -0
  94. package/dist/find-projects.js +16 -0
  95. package/dist/fresh.d.ts +1 -0
  96. package/dist/fresh.js +17 -0
  97. package/dist/generate/action-types.d.ts +1 -0
  98. package/dist/generate/action-types.js +13 -0
  99. package/dist/generate/component-meta.d.ts +1 -0
  100. package/dist/generate/component-meta.js +9 -0
  101. package/dist/generate/custom-cli-file.d.ts +1 -0
  102. package/dist/generate/custom-cli-file.js +21 -0
  103. package/dist/generate/env-files.d.ts +1 -0
  104. package/dist/generate/env-files.js +66 -0
  105. package/dist/generate/ide-helpers.d.ts +1 -0
  106. package/dist/generate/ide-helpers.js +8 -0
  107. package/dist/generate/index.d.ts +36 -0
  108. package/dist/generate/index.js +158 -0
  109. package/dist/generate/lib-entries.d.ts +1 -0
  110. package/dist/generate/lib-entries.js +16 -0
  111. package/dist/generate/pantry-file.d.ts +1 -0
  112. package/dist/generate/pantry-file.js +46 -0
  113. package/dist/generate/symlink-core.d.ts +1 -0
  114. package/dist/generate/symlink-core.js +2 -0
  115. package/dist/generate/types.d.ts +1 -0
  116. package/dist/generate/types.js +3 -0
  117. package/dist/generate/vscode-custom-data.d.ts +1 -0
  118. package/dist/generate/vscode-custom-data.js +9 -0
  119. package/dist/generate/web-types.d.ts +1 -0
  120. package/dist/generate/web-types.js +0 -0
  121. package/dist/helpers/component-meta.d.ts +26 -0
  122. package/dist/helpers/component-meta.js +295 -0
  123. package/dist/helpers/index.d.ts +4 -0
  124. package/dist/helpers/index.js +1 -0
  125. package/dist/helpers/lib-entries.d.ts +12 -0
  126. package/dist/helpers/lib-entries.js +73 -0
  127. package/dist/helpers/package-json.d.ts +2 -0
  128. package/dist/helpers/package-json.js +74 -0
  129. package/dist/helpers/utils.d.ts +23 -0
  130. package/dist/helpers/utils.js +105 -0
  131. package/dist/helpers/vscode-custom-data.d.ts +3 -0
  132. package/dist/helpers/vscode-custom-data.js +30 -0
  133. package/dist/index.d.ts +62 -0
  134. package/dist/index.js +50 -0
  135. package/dist/key-generate.d.ts +1 -0
  136. package/dist/key-generate.js +24 -0
  137. package/dist/lint/fix.d.ts +1 -0
  138. package/dist/lint/fix.js +4 -0
  139. package/dist/lint/index.d.ts +1 -0
  140. package/dist/lint/index.js +4 -0
  141. package/dist/lint/lint.d.ts +14 -0
  142. package/dist/lint/lint.js +34 -0
  143. package/dist/make-command.d.ts +10 -0
  144. package/dist/make-command.js +95 -0
  145. package/dist/make-job.d.ts +5 -0
  146. package/dist/make-job.js +154 -0
  147. package/dist/make-policy.d.ts +10 -0
  148. package/dist/make-policy.js +142 -0
  149. package/dist/make-resource.d.ts +10 -0
  150. package/dist/make-resource.js +142 -0
  151. package/dist/make.d.ts +52 -0
  152. package/dist/make.js +328 -0
  153. package/dist/migrate/database.d.ts +1 -0
  154. package/dist/migrate/database.js +16 -0
  155. package/dist/migrate/fresh.d.ts +1 -0
  156. package/dist/migrate/fresh.js +31 -0
  157. package/dist/orm/base.d.ts +1 -0
  158. package/dist/orm/base.js +0 -0
  159. package/dist/orm/test.d.ts +1 -0
  160. package/dist/orm/test.js +0 -0
  161. package/dist/prepublish.d.ts +1 -0
  162. package/dist/prepublish.js +16 -0
  163. package/dist/queue/clear.d.ts +1 -0
  164. package/dist/queue/clear.js +42 -0
  165. package/dist/queue/dlq-purge.d.ts +1 -0
  166. package/dist/queue/dlq-purge.js +28 -0
  167. package/dist/queue/dlq-retry.d.ts +1 -0
  168. package/dist/queue/dlq-retry.js +35 -0
  169. package/dist/queue/dlq.d.ts +1 -0
  170. package/dist/queue/dlq.js +55 -0
  171. package/dist/queue/failed.d.ts +1 -0
  172. package/dist/queue/failed.js +69 -0
  173. package/dist/queue/flush.d.ts +1 -0
  174. package/dist/queue/flush.js +49 -0
  175. package/dist/queue/inspect.d.ts +1 -0
  176. package/dist/queue/inspect.js +154 -0
  177. package/dist/queue/list.d.ts +1 -0
  178. package/dist/queue/list.js +69 -0
  179. package/dist/queue/monitor.d.ts +1 -0
  180. package/dist/queue/monitor.js +95 -0
  181. package/dist/queue/pause.d.ts +1 -0
  182. package/dist/queue/pause.js +28 -0
  183. package/dist/queue/quarantine.d.ts +1 -0
  184. package/dist/queue/quarantine.js +42 -0
  185. package/dist/queue/resume.d.ts +1 -0
  186. package/dist/queue/resume.js +27 -0
  187. package/dist/queue/retry.d.ts +1 -0
  188. package/dist/queue/retry.js +41 -0
  189. package/dist/queue/schedule-list.d.ts +1 -0
  190. package/dist/queue/schedule-list.js +33 -0
  191. package/dist/queue/schedule.d.ts +1 -0
  192. package/dist/queue/schedule.js +12 -0
  193. package/dist/queue/status.d.ts +1 -0
  194. package/dist/queue/status.js +68 -0
  195. package/dist/queue/table.d.ts +1 -0
  196. package/dist/queue/table.js +10 -0
  197. package/dist/queue/unquarantine.d.ts +1 -0
  198. package/dist/queue/unquarantine.js +27 -0
  199. package/dist/queue/work.d.ts +1 -0
  200. package/dist/queue/work.js +57 -0
  201. package/dist/release.d.ts +1 -0
  202. package/dist/release.js +21 -0
  203. package/dist/route/list.d.ts +1 -0
  204. package/dist/route/list.js +78 -0
  205. package/dist/run-action.d.ts +2 -0
  206. package/dist/run-action.js +1 -0
  207. package/dist/saas/setup.d.ts +1 -0
  208. package/dist/saas/setup.js +9 -0
  209. package/dist/scaffold-crud.d.ts +25 -0
  210. package/dist/scaffold-crud.js +211 -0
  211. package/dist/schedule/run.d.ts +1 -0
  212. package/dist/schedule/run.js +7 -0
  213. package/dist/search/flush.d.ts +1 -0
  214. package/dist/search/flush.js +10 -0
  215. package/dist/search/import.d.ts +1 -0
  216. package/dist/search/import.js +10 -0
  217. package/dist/search/settings-list.d.ts +1 -0
  218. package/dist/search/settings-list.js +10 -0
  219. package/dist/search/settings.d.ts +1 -0
  220. package/dist/search/settings.js +9 -0
  221. package/dist/serve/api.d.ts +1 -0
  222. package/dist/serve/api.js +35 -0
  223. package/dist/setup/index.d.ts +1 -0
  224. package/dist/setup/index.js +1 -0
  225. package/dist/setup/ssl.d.ts +38 -0
  226. package/dist/setup/ssl.js +271 -0
  227. package/dist/stacks.d.ts +4 -0
  228. package/dist/stacks.js +265 -0
  229. package/dist/templates.d.ts +28 -0
  230. package/dist/templates.js +301 -0
  231. package/dist/test/feature.d.ts +1 -0
  232. package/dist/test/feature.js +11 -0
  233. package/dist/test/index.d.ts +1 -0
  234. package/dist/test/index.js +11 -0
  235. package/dist/test/ui.d.ts +1 -0
  236. package/dist/test/ui.js +4 -0
  237. package/dist/test/unit.d.ts +1 -0
  238. package/dist/test/unit.js +11 -0
  239. package/dist/typecheck.d.ts +1 -0
  240. package/dist/typecheck.js +4 -0
  241. package/dist/types.d.ts +3 -0
  242. package/dist/types.js +16 -0
  243. package/dist/upgrade/binary.d.ts +1 -0
  244. package/dist/upgrade/binary.js +29 -0
  245. package/dist/upgrade/bun.d.ts +1 -0
  246. package/dist/upgrade/bun.js +0 -0
  247. package/dist/upgrade/dependencies.d.ts +1 -0
  248. package/dist/upgrade/dependencies.js +0 -0
  249. package/dist/upgrade/framework-utils.d.ts +150 -0
  250. package/dist/upgrade/framework-utils.js +193 -0
  251. package/dist/upgrade/framework.d.ts +1 -0
  252. package/dist/upgrade/framework.js +310 -0
  253. package/dist/upgrade/index.d.ts +1 -0
  254. package/dist/upgrade/index.js +17 -0
  255. package/dist/upgrade/packages.d.ts +14 -0
  256. package/dist/upgrade/packages.js +99 -0
  257. package/dist/upgrade/shell.d.ts +1 -0
  258. package/dist/upgrade/shell.js +41 -0
  259. package/dist/upgrade.d.ts +7 -0
  260. package/dist/upgrade.js +38 -0
  261. package/package.json +16 -16
@@ -0,0 +1,38 @@
1
+ import type { CliOptions } from '@stacksjs/types';
2
+ /**
3
+ * Check if a domain is in the hosts file
4
+ */
5
+ export declare function isDomainInHosts(domain: string): boolean;
6
+ /**
7
+ * Add a domain to the hosts file (requires sudo on Unix)
8
+ */
9
+ export declare function addDomainToHosts(domain: string, verbose?: boolean): Promise<boolean>;
10
+ /**
11
+ * Check if SSL certificates exist for a domain
12
+ */
13
+ export declare function sslCertificatesExist(domain: string): boolean;
14
+ /**
15
+ * Check if a certificate is trusted (macOS only for now)
16
+ */
17
+ export declare function isCertificateTrusted(domain: string): boolean;
18
+ /**
19
+ * Trust a certificate on macOS
20
+ */
21
+ export declare function trustCertificate(domain: string, verbose?: boolean): Promise<boolean>;
22
+ /**
23
+ * Generate SSL certificates for a domain using rpx
24
+ */
25
+ export declare function generateCertificates(domain: string, verbose?: boolean): Promise<boolean>;
26
+ /**
27
+ * Complete SSL setup for development
28
+ */
29
+ export declare function setupSSL(options?: SSLSetupOptions): Promise<boolean>;
30
+ /**
31
+ * Check if SSL setup is complete for a domain
32
+ */
33
+ export declare function isSSLSetupComplete(domain: string): boolean;
34
+ export declare interface SSLSetupOptions extends CliOptions {
35
+ domain?: string
36
+ skipHosts?: boolean
37
+ skipTrust?: boolean
38
+ }
@@ -0,0 +1,271 @@
1
+ import { execSync, spawn } from "node:child_process";
2
+ import fs from "node:fs";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+ import process from "node:process";
6
+ import { log } from "@stacksjs/cli";
7
+ const HOSTS_FILE = process.platform === "win32" ? path.join(process.env.windir || "C:\\Windows", "System32", "drivers", "etc", "hosts") : "/etc/hosts", SSL_DIR = path.join(os.homedir(), ".stacks", "ssl");
8
+ function isInteractiveTerminal() {
9
+ return process.stdin.isTTY === !0 && process.stdout.isTTY === !0;
10
+ }
11
+ function getSudoPassword() {
12
+ return process.env.SUDO_PASSWORD;
13
+ }
14
+ function canRunSudo() {
15
+ return isInteractiveTerminal() || !!getSudoPassword();
16
+ }
17
+ export function isDomainInHosts(domain) {
18
+ try {
19
+ const hostsContent = fs.readFileSync(HOSTS_FILE, "utf-8");
20
+ return hostsContent.includes(`127.0.0.1 ${domain}`) || hostsContent.includes(`::1 ${domain}`);
21
+ } catch {
22
+ if (process.platform !== "win32") {
23
+ const sudoPassword = getSudoPassword();
24
+ if (sudoPassword)
25
+ try {
26
+ const result = execSync(`echo '${sudoPassword}' | sudo -S cat "${HOSTS_FILE}" 2>/dev/null`, { encoding: "utf-8" });
27
+ return result.includes(`127.0.0.1 ${domain}`) || result.includes(`::1 ${domain}`);
28
+ } catch {
29
+ return !1;
30
+ }
31
+ }
32
+ return !1;
33
+ }
34
+ }
35
+ export async function addDomainToHosts(domain, verbose) {
36
+ if (isDomainInHosts(domain)) {
37
+ if (verbose)
38
+ log.info(`${domain} already in hosts file`);
39
+ return !0;
40
+ }
41
+ const hostsEntry = `
42
+ # Added by Stacks
43
+ 127.0.0.1 ${domain}
44
+ ::1 ${domain}
45
+ `;
46
+ if (process.platform === "win32") {
47
+ log.warn("Please add this to your hosts file manually:");
48
+ log.warn(`127.0.0.1 ${domain}`);
49
+ log.warn(`::1 ${domain}`);
50
+ log.warn(`File location: ${HOSTS_FILE}`);
51
+ return !1;
52
+ }
53
+ if (!canRunSudo()) {
54
+ if (verbose) {
55
+ log.warn(`Cannot add ${domain} to hosts file (non-interactive mode and no SUDO_PASSWORD set)`);
56
+ log.info("Run `buddy setup:ssl` in a terminal or set SUDO_PASSWORD environment variable");
57
+ }
58
+ return !1;
59
+ }
60
+ const sudoPassword = getSudoPassword();
61
+ return new Promise((resolve) => {
62
+ let child;
63
+ if (sudoPassword)
64
+ child = spawn("sh", ["-c", `echo '${sudoPassword}' | sudo -S sh -c "echo '${hostsEntry}' >> '${HOSTS_FILE}'"`], {
65
+ stdio: ["pipe", "inherit", "inherit"]
66
+ });
67
+ else
68
+ child = spawn("sudo", ["sh", "-c", `echo '${hostsEntry}' >> "${HOSTS_FILE}"`], {
69
+ stdio: "inherit"
70
+ });
71
+ child.on("close", (code) => {
72
+ if (code === 0) {
73
+ if (verbose)
74
+ log.success(`Added ${domain} to hosts file`);
75
+ resolve(!0);
76
+ } else {
77
+ if (verbose) {
78
+ log.error(`Failed to add ${domain} to hosts file`);
79
+ log.warn(`Manually add: 127.0.0.1 ${domain}`);
80
+ }
81
+ resolve(!1);
82
+ }
83
+ });
84
+ child.on("error", () => {
85
+ log.error("Failed to run sudo command");
86
+ resolve(!1);
87
+ });
88
+ });
89
+ }
90
+ export function sslCertificatesExist(domain) {
91
+ const certPath = path.join(SSL_DIR, `${domain}.crt`), keyPath = path.join(SSL_DIR, `${domain}.key`), caPath = path.join(SSL_DIR, `${domain}.ca.crt`);
92
+ return fs.existsSync(certPath) && fs.existsSync(keyPath) && fs.existsSync(caPath);
93
+ }
94
+ export function isCertificateTrusted(domain) {
95
+ if (process.platform !== "darwin")
96
+ return !0;
97
+ const certPath = path.join(SSL_DIR, `${domain}.crt`);
98
+ if (!fs.existsSync(certPath))
99
+ return !1;
100
+ try {
101
+ const fingerprint = execSync(`openssl x509 -noout -fingerprint -sha256 -in "${certPath}"`, { encoding: "utf-8" }), eqIdx = fingerprint.indexOf("="), fingerprintValue = eqIdx >= 0 ? fingerprint.slice(eqIdx + 1).trim() : "";
102
+ if (!fingerprintValue)
103
+ return !1;
104
+ return execSync("security find-certificate -a -Z -p | openssl x509 -noout -fingerprint -sha256 2>/dev/null || true", { encoding: "utf-8" }).includes(fingerprintValue);
105
+ } catch {
106
+ return !1;
107
+ }
108
+ }
109
+ export async function trustCertificate(domain, verbose) {
110
+ const caCertPath = path.join(SSL_DIR, `${domain}.ca.crt`), certPath = path.join(SSL_DIR, `${domain}.crt`);
111
+ if (!fs.existsSync(caCertPath)) {
112
+ log.error(`CA certificate not found: ${caCertPath}`);
113
+ return !1;
114
+ }
115
+ if (isCertificateTrusted(domain)) {
116
+ if (verbose)
117
+ log.info("Certificate is already trusted, skipping trust step");
118
+ return !0;
119
+ }
120
+ if (!canRunSudo()) {
121
+ if (verbose) {
122
+ log.warn("Cannot trust certificate (non-interactive mode and no SUDO_PASSWORD set)");
123
+ log.info("Run `buddy setup:ssl` in a terminal or set SUDO_PASSWORD environment variable");
124
+ }
125
+ return !1;
126
+ }
127
+ const sudoPassword = getSudoPassword();
128
+ if (process.platform === "darwin")
129
+ return new Promise((resolve) => {
130
+ const combinedCommand = `security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain '${caCertPath}' && security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain '${certPath}'`;
131
+ let child;
132
+ if (sudoPassword)
133
+ child = spawn("sh", ["-c", `echo '${sudoPassword}' | sudo -S sh -c "${combinedCommand}"`], {
134
+ stdio: ["pipe", "inherit", "inherit"]
135
+ });
136
+ else
137
+ child = spawn("sudo", ["sh", "-c", combinedCommand], {
138
+ stdio: "inherit"
139
+ });
140
+ child.on("close", (code) => {
141
+ if (code === 0) {
142
+ try {
143
+ execSync(`security add-trusted-cert -d -r trustRoot -k ~/Library/Keychains/login.keychain-db "${certPath}" 2>/dev/null || true`);
144
+ } catch {}
145
+ resolve(!0);
146
+ } else {
147
+ if (verbose) {
148
+ log.warn("Could not trust certificate automatically");
149
+ log.info(` sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain "${caCertPath}"`);
150
+ }
151
+ resolve(!1);
152
+ }
153
+ });
154
+ child.on("error", () => {
155
+ log.error("Failed to run sudo command");
156
+ resolve(!1);
157
+ });
158
+ });
159
+ else if (process.platform === "linux")
160
+ return new Promise((resolve) => {
161
+ const linuxCommands = `mkdir -p /usr/local/share/ca-certificates/stacks && cp '${caCertPath}' /usr/local/share/ca-certificates/stacks/ && update-ca-certificates`;
162
+ let child;
163
+ if (sudoPassword)
164
+ child = spawn("sh", ["-c", `echo '${sudoPassword}' | sudo -S sh -c "${linuxCommands}"`], {
165
+ stdio: ["pipe", "inherit", "inherit"]
166
+ });
167
+ else
168
+ child = spawn("sudo", ["sh", "-c", linuxCommands], {
169
+ stdio: "inherit"
170
+ });
171
+ child.on("close", (code) => {
172
+ if (code === 0) {
173
+ log.success("Certificate trusted");
174
+ resolve(!0);
175
+ } else {
176
+ log.warn("Could not trust certificate automatically");
177
+ resolve(!1);
178
+ }
179
+ });
180
+ child.on("error", () => resolve(!1));
181
+ });
182
+ else if (process.platform === "win32") {
183
+ log.warn("Please manually trust the certificate in Windows:");
184
+ log.warn(` 1. Double-click: ${caCertPath}`);
185
+ log.warn(' 2. Click "Install Certificate"');
186
+ log.warn(' 3. Select "Local Machine" and place in "Trusted Root Certification Authorities"');
187
+ return !1;
188
+ }
189
+ return !1;
190
+ }
191
+ export async function generateCertificates(domain, verbose) {
192
+ try {
193
+ const tlsxModule = await import("@stacksjs/tlsx"), { generateCertificate, createRootCA } = tlsxModule, _httpsConfig = tlsxModule.httpsConfig;
194
+ if (!fs.existsSync(SSL_DIR))
195
+ fs.mkdirSync(SSL_DIR, { recursive: !0 });
196
+ const config = {
197
+ domain,
198
+ hostCertCN: domain,
199
+ basePath: SSL_DIR,
200
+ caCertPath: path.join(SSL_DIR, `${domain}.ca.crt`),
201
+ certPath: path.join(SSL_DIR, `${domain}.crt`),
202
+ keyPath: path.join(SSL_DIR, `${domain}.key`),
203
+ altNameIPs: ["127.0.0.1", "::1"],
204
+ altNameURIs: ["localhost", domain],
205
+ organizationName: "Stacks Development",
206
+ countryName: "US",
207
+ stateName: "California",
208
+ localityName: "Playa Vista",
209
+ commonName: domain,
210
+ validityDays: 825,
211
+ verbose: verbose || !1,
212
+ subjectAltNames: [
213
+ { type: 2, value: domain },
214
+ { type: 2, value: `*.${domain.split(".").slice(1).join(".")}` },
215
+ { type: 2, value: "localhost" },
216
+ { type: 2, value: "*.localhost" }
217
+ ]
218
+ };
219
+ if (verbose)
220
+ log.info("Generating Root CA certificate...");
221
+ const caCert = await createRootCA(config);
222
+ if (verbose)
223
+ log.info(`Generating host certificate for ${domain}...`);
224
+ const hostCert = await generateCertificate({
225
+ ...config,
226
+ rootCA: {
227
+ certificate: caCert.certificate,
228
+ privateKey: caCert.privateKey
229
+ }
230
+ });
231
+ fs.writeFileSync(config.caCertPath, caCert.certificate);
232
+ fs.writeFileSync(config.certPath, hostCert.certificate);
233
+ fs.writeFileSync(config.keyPath, hostCert.privateKey);
234
+ if (verbose)
235
+ log.success(`SSL certificates generated for ${domain}`);
236
+ return !0;
237
+ } catch (error) {
238
+ log.error(`Failed to generate certificates: ${error}`);
239
+ return !1;
240
+ }
241
+ }
242
+ export async function setupSSL(options = {}) {
243
+ const domain = options.domain || process.env.APP_URL?.replace(/^https?:\/\//, "") || "stacks.localhost";
244
+ if (domain === "localhost" || domain.startsWith("localhost:")) {
245
+ log.info("Skipping SSL setup for localhost");
246
+ return !0;
247
+ }
248
+ let success = !0;
249
+ if (!options.skipHosts) {
250
+ if (!await addDomainToHosts(domain, options.verbose) && options.verbose)
251
+ log.warn("Hosts file not updated - you may need to add the entry manually");
252
+ }
253
+ if (!sslCertificatesExist(domain)) {
254
+ if (options.verbose)
255
+ log.info("Generating SSL certificates...");
256
+ if (!await generateCertificates(domain, options.verbose)) {
257
+ log.error("Failed to generate SSL certificates");
258
+ return !1;
259
+ }
260
+ }
261
+ if (!options.skipTrust && !isCertificateTrusted(domain)) {
262
+ if (!await trustCertificate(domain, options.verbose)) {
263
+ log.warn("Certificate not trusted - you may see browser warnings");
264
+ success = !1;
265
+ }
266
+ }
267
+ return success;
268
+ }
269
+ export function isSSLSetupComplete(domain) {
270
+ return isDomainInHosts(domain) && sslCertificatesExist(domain);
271
+ }
@@ -0,0 +1,4 @@
1
+ import type { StackInstallOptions, StackListEntry, StackManifestEntry, StackUninstallOptions } from '@stacksjs/types';
2
+ export declare function installStack(options: StackInstallOptions): Promise<StackManifestEntry | null>;
3
+ export declare function uninstallStack(options: StackUninstallOptions): Promise<boolean>;
4
+ export declare function listStacks(): Promise<StackListEntry[]>;
package/dist/stacks.js ADDED
@@ -0,0 +1,265 @@
1
+ import { log } from "@stacksjs/logging";
2
+ import { dirname, join, projectPath, relative, stacksBackupPath, stacksLockPath } from "@stacksjs/path";
3
+ import { copyFile, createFolder, deleteFile, deleteEmptyFolders, doesFolderExist, fs } from "@stacksjs/storage";
4
+ const STACK_DIRS = ["app", "config", "database", "resources", "routes", "public", "locales"];
5
+ async function readStackLock() {
6
+ const lockPath = stacksLockPath();
7
+ try {
8
+ if (fs.existsSync(lockPath)) {
9
+ const content = await Bun.file(lockPath).text();
10
+ return JSON.parse(content);
11
+ }
12
+ } catch {}
13
+ return {
14
+ version: 1,
15
+ generatedAt: new Date().toISOString(),
16
+ stacks: {}
17
+ };
18
+ }
19
+ async function writeStackLock(lock) {
20
+ const lockPath = stacksLockPath(), lockDir = dirname(lockPath);
21
+ if (!fs.existsSync(lockDir))
22
+ await createFolder(lockDir);
23
+ lock.generatedAt = new Date().toISOString();
24
+ await Bun.write(lockPath, JSON.stringify(lock, null, 2));
25
+ }
26
+ function resolveStackPackagePath(name) {
27
+ const pantryDir = projectPath("pantry"), directPath = join(pantryDir, name);
28
+ if (fs.existsSync(join(directPath, "package.json")))
29
+ return directPath;
30
+ const scopedPath = join(pantryDir, "@stacksjs", name);
31
+ if (fs.existsSync(join(scopedPath, "package.json")))
32
+ return scopedPath;
33
+ if (name.startsWith("@")) {
34
+ const fullPath = join(pantryDir, name);
35
+ if (fs.existsSync(join(fullPath, "package.json")))
36
+ return fullPath;
37
+ }
38
+ const patterns = ["*/package.json", "@*/*/package.json"];
39
+ for (const pattern of patterns) {
40
+ const glob = new Bun.Glob(pattern);
41
+ for (const file of glob.scanSync({ cwd: pantryDir, absolute: !0, onlyFiles: !0 }))
42
+ try {
43
+ if (JSON.parse(fs.readFileSync(file, "utf-8"))?.stacks?.name === name)
44
+ return dirname(file);
45
+ } catch {}
46
+ }
47
+ return null;
48
+ }
49
+ function collectFiles(dir, baseDir) {
50
+ const files = [];
51
+ if (!fs.existsSync(dir))
52
+ return files;
53
+ const entries = fs.readdirSync(dir, { withFileTypes: !0 });
54
+ for (const entry of entries) {
55
+ const fullPath = join(dir, entry.name);
56
+ if (entry.isDirectory())
57
+ files.push(...collectFiles(fullPath, baseDir));
58
+ else
59
+ files.push(relative(baseDir, fullPath));
60
+ }
61
+ return files;
62
+ }
63
+ export async function installStack(options) {
64
+ const { name, force = !1, dryRun = !1, verbose = !1 } = options, conflict = force ? "overwrite" : options.conflict ?? "skip";
65
+ log.info(`Installing stack "${name}"...`);
66
+ const stackPath = resolveStackPackagePath(name);
67
+ if (!stackPath) {
68
+ log.error(`Stack "${name}" not found in pantry. Install it first with: bun add <package-name>`);
69
+ return null;
70
+ }
71
+ let pkg, meta;
72
+ try {
73
+ const pkgContent = await Bun.file(join(stackPath, "package.json")).text();
74
+ pkg = JSON.parse(pkgContent);
75
+ meta = pkg.stacks;
76
+ if (!meta?.name) {
77
+ log.error(`Package at "${stackPath}" does not have a valid "stacks" field in package.json`);
78
+ return null;
79
+ }
80
+ } catch (error) {
81
+ log.error(`Failed to read stack package.json: ${error}`);
82
+ return null;
83
+ }
84
+ const lock = await readStackLock(), packageName = pkg.name || name;
85
+ if (lock.stacks[packageName] && !force) {
86
+ log.warn(`Stack "${meta.name}" is already installed. Use --force to reinstall.`);
87
+ return lock.stacks[packageName];
88
+ }
89
+ const dirsToScan = meta.directories ?? STACK_DIRS, allFiles = [];
90
+ for (const dir of dirsToScan) {
91
+ const dirPath = join(stackPath, dir);
92
+ if (fs.existsSync(dirPath)) {
93
+ const files = collectFiles(dirPath, stackPath);
94
+ allFiles.push(...files);
95
+ }
96
+ }
97
+ if (allFiles.length === 0) {
98
+ log.warn(`Stack "${meta.name}" has no files to install`);
99
+ return null;
100
+ }
101
+ if (verbose)
102
+ log.info(`Found ${allFiles.length} file(s) to install`);
103
+ const copiedFiles = [], skippedFiles = [];
104
+ for (const relPath of allFiles) {
105
+ const src = join(stackPath, relPath), dest = projectPath(relPath);
106
+ if (dryRun) {
107
+ if (fs.existsSync(dest)) {
108
+ log.info(`[dry-run] Would ${conflict}: ${relPath}`);
109
+ if (conflict === "skip")
110
+ skippedFiles.push(relPath);
111
+ else
112
+ copiedFiles.push(relPath);
113
+ } else {
114
+ log.info(`[dry-run] Would copy: ${relPath}`);
115
+ copiedFiles.push(relPath);
116
+ }
117
+ continue;
118
+ }
119
+ if (fs.existsSync(dest))
120
+ switch (conflict) {
121
+ case "skip":
122
+ if (verbose)
123
+ log.warn(`Skipping (file exists): ${relPath}`);
124
+ skippedFiles.push(relPath);
125
+ continue;
126
+ case "backup": {
127
+ const backupDir = stacksBackupPath(meta.name), backupDest = join(backupDir, relPath), backupDestDir = dirname(backupDest);
128
+ if (!fs.existsSync(backupDestDir))
129
+ await createFolder(backupDestDir);
130
+ copyFile(dest, backupDest);
131
+ if (verbose)
132
+ log.info(`Backed up: ${relPath}`);
133
+ break;
134
+ }
135
+ case "overwrite":
136
+ if (verbose)
137
+ log.warn(`Overwriting: ${relPath}`);
138
+ break;
139
+ }
140
+ const destDir = dirname(dest);
141
+ if (!fs.existsSync(destDir))
142
+ await createFolder(destDir);
143
+ copyFile(src, dest);
144
+ copiedFiles.push(relPath);
145
+ if (verbose)
146
+ log.info(`Copied: ${relPath}`);
147
+ }
148
+ if (dryRun) {
149
+ log.info(`[dry-run] Would install ${copiedFiles.length} file(s), skip ${skippedFiles.length} file(s)`);
150
+ return null;
151
+ }
152
+ const entry = {
153
+ packageName,
154
+ name: meta.name,
155
+ version: pkg.version || "0.0.0",
156
+ installedAt: new Date().toISOString(),
157
+ files: copiedFiles,
158
+ skipped: skippedFiles,
159
+ conflictStrategy: conflict
160
+ };
161
+ lock.stacks[packageName] = entry;
162
+ await writeStackLock(lock);
163
+ log.success(`Installed stack "${meta.name}" (${copiedFiles.length} file(s) copied, ${skippedFiles.length} skipped)`);
164
+ return entry;
165
+ }
166
+ export async function uninstallStack(options) {
167
+ const { name, force = !1, verbose = !1 } = options;
168
+ log.info(`Uninstalling stack "${name}"...`);
169
+ const lock = await readStackLock();
170
+ let entryKey = null, entry = null;
171
+ for (const [key, value] of Object.entries(lock.stacks))
172
+ if (value.name === name || key === name) {
173
+ entryKey = key;
174
+ entry = value;
175
+ break;
176
+ }
177
+ if (!entry || !entryKey) {
178
+ log.error(`Stack "${name}" is not installed`);
179
+ return !1;
180
+ }
181
+ let removedCount = 0;
182
+ for (const relPath of entry.files) {
183
+ const filePath = projectPath(relPath);
184
+ if (!fs.existsSync(filePath)) {
185
+ if (verbose)
186
+ log.info(`Already removed: ${relPath}`);
187
+ continue;
188
+ }
189
+ if (!force) {
190
+ const stackPath = resolveStackPackagePath(name);
191
+ if (stackPath) {
192
+ const srcPath = join(stackPath, relPath);
193
+ if (fs.existsSync(srcPath)) {
194
+ const srcContent = await Bun.file(srcPath).text(), destContent = await Bun.file(filePath).text();
195
+ if (srcContent !== destContent) {
196
+ log.warn(`Skipping modified file: ${relPath} (use --force to remove)`);
197
+ continue;
198
+ }
199
+ }
200
+ }
201
+ }
202
+ await deleteFile(filePath);
203
+ removedCount++;
204
+ if (verbose)
205
+ log.info(`Removed: ${relPath}`);
206
+ }
207
+ const backupDir = stacksBackupPath(entry.name);
208
+ if (fs.existsSync(backupDir) && doesFolderExist(backupDir)) {
209
+ const backupFiles = collectFiles(backupDir, backupDir);
210
+ for (const relPath of backupFiles) {
211
+ const backupSrc = join(backupDir, relPath), dest = projectPath(relPath), destDir = dirname(dest);
212
+ if (!fs.existsSync(destDir))
213
+ await createFolder(destDir);
214
+ copyFile(backupSrc, dest);
215
+ if (verbose)
216
+ log.info(`Restored from backup: ${relPath}`);
217
+ }
218
+ fs.rmSync(backupDir, { recursive: !0, force: !0 });
219
+ }
220
+ for (const dir of STACK_DIRS) {
221
+ const dirPath = projectPath(dir);
222
+ if (fs.existsSync(dirPath))
223
+ await deleteEmptyFolders(dirPath);
224
+ }
225
+ delete lock.stacks[entryKey];
226
+ await writeStackLock(lock);
227
+ log.success(`Uninstalled stack "${entry.name}" (${removedCount} file(s) removed)`);
228
+ return !0;
229
+ }
230
+ export async function listStacks() {
231
+ const entries = [], lock = await readStackLock(), pantryDir = projectPath("pantry");
232
+ if (fs.existsSync(pantryDir)) {
233
+ const patterns = ["*/package.json", "@*/*/package.json"];
234
+ for (const pattern of patterns) {
235
+ const glob = new Bun.Glob(pattern);
236
+ for (const file of glob.scanSync({ cwd: pantryDir, absolute: !0, onlyFiles: !0 }))
237
+ try {
238
+ const pkgContent = fs.readFileSync(file, "utf-8"), pkg = JSON.parse(pkgContent);
239
+ if (!pkg?.stacks?.name)
240
+ continue;
241
+ const packageName = pkg.name, installed = !!lock.stacks[packageName], lockEntry = lock.stacks[packageName];
242
+ entries.push({
243
+ name: pkg.stacks.name,
244
+ packageName,
245
+ version: pkg.version || "0.0.0",
246
+ description: pkg.stacks.description,
247
+ installed,
248
+ installedAt: lockEntry?.installedAt,
249
+ fileCount: lockEntry?.files.length
250
+ });
251
+ } catch {}
252
+ }
253
+ }
254
+ for (const [packageName, entry] of Object.entries(lock.stacks))
255
+ if (!entries.some((e) => e.packageName === packageName))
256
+ entries.push({
257
+ name: entry.name,
258
+ packageName,
259
+ version: entry.version,
260
+ installed: !0,
261
+ installedAt: entry.installedAt,
262
+ fileCount: entry.files.length
263
+ });
264
+ return entries;
265
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Code generation templates for the make command
3
+ *
4
+ * These templates use the {0}, {1}, etc. format for template substitution
5
+ * which works with the @stacksjs/strings template function
6
+ */
7
+ export declare const CODE_TEMPLATES: {
8
+ action: string;
9
+ actionWithValidation: string;
10
+ actionWithAuth: string;
11
+ actionWithBoth: string;
12
+ component: string;
13
+ page: string;
14
+ function: string;
15
+ language: string;
16
+ notification: string;
17
+ mail: string;
18
+ mailTemplate: string;
19
+ middleware: string;
20
+ model: string;
21
+ migration: string;
22
+ stackPackageJson: string;
23
+ job: string;
24
+ event: string;
25
+ listener: string;
26
+ command: string
27
+ };
28
+ export type TemplateKey = keyof typeof CODE_TEMPLATES;