@stacksjs/actions 0.74.46 → 0.74.47
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/auth/client.js +1 -1
- package/dist/auth/setup.js +1 -201
- package/dist/bump.js +1 -1
- package/dist/dev/dashboard-proxy.d.ts +21 -0
- package/dist/dev/dashboard-proxy.js +1 -0
- package/dist/dev/dashboard-utils.d.ts +15 -1
- package/dist/dev/dashboard-utils.js +1 -1
- package/dist/dev/dashboard.js +3 -3
- package/dist/dev/defaults-resources.d.ts +11 -0
- package/dist/dev/defaults-resources.js +1 -1
- package/dist/serve/dashboard.js +1 -1
- package/package.json +40 -39
package/dist/auth/client.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import process from"node:process";import{
|
|
1
|
+
import process from"node:process";import{createClient}from"@stacksjs/auth";import{ensureDatabaseConfigLoaded}from"@stacksjs/database";import{log}from"@stacksjs/logging";const args=process.argv.slice(2),getArg=(name)=>{const idx=args.findIndex((a)=>a.startsWith(`--${name}=`)||a.startsWith(`-${name.charAt(0)}=`));if(idx!==-1){const arg=args[idx];if(!arg)return;const eqIdx=arg.indexOf("=");return eqIdx>=0?arg.slice(eqIdx+1):void 0}const flagIdx=args.findIndex((a)=>a===`--${name}`||a===`-${name.charAt(0)}`),value=flagIdx!==-1?args[flagIdx+1]:void 0;if(value&&!value.startsWith("-"))return value;return},hasFlag=(name)=>{return args.includes(`--${name}`)},name=getArg("name")||"OAuth Client",redirect=getArg("redirect")||"http://localhost",isPersonalAccess=hasFlag("personal"),isPasswordClient=hasFlag("password");log.info(`Creating OAuth client: ${name}`);await ensureDatabaseConfigLoaded();const{client,plainTextSecret}=await createClient({name,redirect,personalAccessClient:isPersonalAccess,passwordClient:isPasswordClient});log.success("OAuth client created successfully");log.info("");log.info("Client Details:");log.info(` Client ID: ${client.id}`);log.info(" Client Secret: [REDACTED]");process.stdout.write(`Client Secret (save now, shown once): ${plainTextSecret}
|
|
2
2
|
`);log.info(` Redirect URI: ${redirect}`);log.info("");log.warn("Make sure to save the client secret. You will not be able to retrieve it again.");await log.flush();process.exit(0);
|
package/dist/auth/setup.js
CHANGED
|
@@ -1,204 +1,4 @@
|
|
|
1
|
-
import process from"node:process";import{
|
|
2
|
-
CREATE TABLE IF NOT EXISTS oauth_clients (
|
|
3
|
-
id SERIAL PRIMARY KEY,
|
|
4
|
-
name VARCHAR(255) NOT NULL,
|
|
5
|
-
secret VARCHAR(100),
|
|
6
|
-
provider VARCHAR(255),
|
|
7
|
-
redirect VARCHAR(2000) NOT NULL,
|
|
8
|
-
personal_access_client BOOLEAN NOT NULL DEFAULT false,
|
|
9
|
-
password_client BOOLEAN NOT NULL DEFAULT false,
|
|
10
|
-
revoked BOOLEAN NOT NULL DEFAULT false,
|
|
11
|
-
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
12
|
-
updated_at TIMESTAMP
|
|
13
|
-
)
|
|
14
|
-
`);else if(isMysql)await db.unsafe(`
|
|
15
|
-
CREATE TABLE IF NOT EXISTS oauth_clients (
|
|
16
|
-
id INTEGER AUTO_INCREMENT PRIMARY KEY,
|
|
17
|
-
name VARCHAR(255) NOT NULL,
|
|
18
|
-
secret VARCHAR(100),
|
|
19
|
-
provider VARCHAR(255),
|
|
20
|
-
redirect VARCHAR(2000) NOT NULL,
|
|
21
|
-
personal_access_client BOOLEAN NOT NULL DEFAULT 0,
|
|
22
|
-
password_client BOOLEAN NOT NULL DEFAULT 0,
|
|
23
|
-
revoked BOOLEAN NOT NULL DEFAULT 0,
|
|
24
|
-
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
25
|
-
updated_at TIMESTAMP
|
|
26
|
-
)
|
|
27
|
-
`);else await db.unsafe(`
|
|
28
|
-
CREATE TABLE IF NOT EXISTS oauth_clients (
|
|
29
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
30
|
-
name VARCHAR(255) NOT NULL,
|
|
31
|
-
secret VARCHAR(100),
|
|
32
|
-
provider VARCHAR(255),
|
|
33
|
-
redirect VARCHAR(2000) NOT NULL,
|
|
34
|
-
personal_access_client BOOLEAN NOT NULL DEFAULT 0,
|
|
35
|
-
password_client BOOLEAN NOT NULL DEFAULT 0,
|
|
36
|
-
revoked BOOLEAN NOT NULL DEFAULT 0,
|
|
37
|
-
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
38
|
-
updated_at TIMESTAMP
|
|
39
|
-
)
|
|
40
|
-
`);if(isPostgres)await db.unsafe(`
|
|
41
|
-
CREATE TABLE IF NOT EXISTS oauth_access_tokens (
|
|
42
|
-
id SERIAL PRIMARY KEY,
|
|
43
|
-
user_id INTEGER NOT NULL,
|
|
44
|
-
oauth_client_id INTEGER NOT NULL,
|
|
45
|
-
token TEXT NOT NULL,
|
|
46
|
-
name VARCHAR(255),
|
|
47
|
-
scopes TEXT,
|
|
48
|
-
revoked BOOLEAN NOT NULL DEFAULT false,
|
|
49
|
-
expires_at TIMESTAMP,
|
|
50
|
-
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
51
|
-
updated_at TIMESTAMP
|
|
52
|
-
)
|
|
53
|
-
`);else if(isMysql)await db.unsafe(`
|
|
54
|
-
CREATE TABLE IF NOT EXISTS oauth_access_tokens (
|
|
55
|
-
id INTEGER AUTO_INCREMENT PRIMARY KEY,
|
|
56
|
-
user_id INTEGER NOT NULL,
|
|
57
|
-
oauth_client_id INTEGER NOT NULL,
|
|
58
|
-
token TEXT NOT NULL,
|
|
59
|
-
name VARCHAR(255),
|
|
60
|
-
scopes TEXT,
|
|
61
|
-
revoked BOOLEAN NOT NULL DEFAULT 0,
|
|
62
|
-
expires_at TIMESTAMP NULL,
|
|
63
|
-
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
64
|
-
updated_at TIMESTAMP NULL
|
|
65
|
-
)
|
|
66
|
-
`);else await db.unsafe(`
|
|
67
|
-
CREATE TABLE IF NOT EXISTS oauth_access_tokens (
|
|
68
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
69
|
-
user_id INTEGER NOT NULL,
|
|
70
|
-
oauth_client_id INTEGER NOT NULL,
|
|
71
|
-
token TEXT NOT NULL,
|
|
72
|
-
name VARCHAR(255),
|
|
73
|
-
scopes TEXT,
|
|
74
|
-
revoked BOOLEAN NOT NULL DEFAULT 0,
|
|
75
|
-
expires_at TIMESTAMP,
|
|
76
|
-
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
77
|
-
updated_at TIMESTAMP
|
|
78
|
-
)
|
|
79
|
-
`);await db.unsafe(`
|
|
80
|
-
CREATE INDEX IF NOT EXISTS idx_oauth_access_tokens_token ON oauth_access_tokens(token)
|
|
81
|
-
`);if(isPostgres)await db.unsafe(`
|
|
82
|
-
CREATE TABLE IF NOT EXISTS oauth_refresh_tokens (
|
|
83
|
-
id SERIAL PRIMARY KEY,
|
|
84
|
-
access_token_id INTEGER NOT NULL,
|
|
85
|
-
token TEXT NOT NULL,
|
|
86
|
-
revoked BOOLEAN NOT NULL DEFAULT false,
|
|
87
|
-
expires_at TIMESTAMP,
|
|
88
|
-
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
89
|
-
)
|
|
90
|
-
`);else if(isMysql)await db.unsafe(`
|
|
91
|
-
CREATE TABLE IF NOT EXISTS oauth_refresh_tokens (
|
|
92
|
-
id INTEGER AUTO_INCREMENT PRIMARY KEY,
|
|
93
|
-
access_token_id INTEGER NOT NULL,
|
|
94
|
-
token TEXT NOT NULL,
|
|
95
|
-
revoked BOOLEAN NOT NULL DEFAULT 0,
|
|
96
|
-
expires_at TIMESTAMP NULL,
|
|
97
|
-
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
98
|
-
)
|
|
99
|
-
`);else await db.unsafe(`
|
|
100
|
-
CREATE TABLE IF NOT EXISTS oauth_refresh_tokens (
|
|
101
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
102
|
-
access_token_id INTEGER NOT NULL,
|
|
103
|
-
token TEXT NOT NULL,
|
|
104
|
-
revoked BOOLEAN NOT NULL DEFAULT 0,
|
|
105
|
-
expires_at TIMESTAMP,
|
|
106
|
-
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
107
|
-
)
|
|
108
|
-
`);await db.unsafe(`
|
|
109
|
-
CREATE INDEX IF NOT EXISTS idx_oauth_refresh_tokens_token ON oauth_refresh_tokens(token)
|
|
110
|
-
`);log.success("OAuth tables ready")}catch(error){await log.error("Failed to create OAuth tables",error);process.exit(1)}log.info("Ensuring password_resets table exists...");try{if(isPostgres)await db.unsafe(`
|
|
111
|
-
CREATE TABLE IF NOT EXISTS password_resets (
|
|
112
|
-
id SERIAL PRIMARY KEY,
|
|
113
|
-
email VARCHAR(255) NOT NULL,
|
|
114
|
-
token VARCHAR(255) NOT NULL,
|
|
115
|
-
expires_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
116
|
-
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
117
|
-
)
|
|
118
|
-
`);else if(isMysql)await db.unsafe(`
|
|
119
|
-
CREATE TABLE IF NOT EXISTS password_resets (
|
|
120
|
-
id INTEGER AUTO_INCREMENT PRIMARY KEY,
|
|
121
|
-
email VARCHAR(255) NOT NULL,
|
|
122
|
-
token VARCHAR(255) NOT NULL,
|
|
123
|
-
expires_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
124
|
-
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
125
|
-
)
|
|
126
|
-
`);else await db.unsafe(`
|
|
127
|
-
CREATE TABLE IF NOT EXISTS password_resets (
|
|
128
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
129
|
-
email VARCHAR(255) NOT NULL,
|
|
130
|
-
token VARCHAR(255) NOT NULL,
|
|
131
|
-
expires_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
132
|
-
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
133
|
-
)
|
|
134
|
-
`);try{if(isPostgres)await db.unsafe("ALTER TABLE password_resets ADD COLUMN IF NOT EXISTS expires_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP");else await db.unsafe("ALTER TABLE password_resets ADD COLUMN expires_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP")}catch{}await db.unsafe(`
|
|
135
|
-
CREATE UNIQUE INDEX IF NOT EXISTS idx_password_resets_email_unique ON password_resets(email)
|
|
136
|
-
`);log.success("Password resets table ready")}catch(error){await log.error("Failed to create password_resets table",error);process.exit(1)}log.info("Ensuring email_verifications table exists...");try{if(isPostgres)await db.unsafe(`
|
|
137
|
-
CREATE TABLE IF NOT EXISTS email_verifications (
|
|
138
|
-
id SERIAL PRIMARY KEY,
|
|
139
|
-
user_id INTEGER NOT NULL,
|
|
140
|
-
token VARCHAR(255) NOT NULL,
|
|
141
|
-
expires_at TIMESTAMP NOT NULL,
|
|
142
|
-
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
143
|
-
)
|
|
144
|
-
`);else if(isMysql)await db.unsafe(`
|
|
145
|
-
CREATE TABLE IF NOT EXISTS email_verifications (
|
|
146
|
-
id INTEGER AUTO_INCREMENT PRIMARY KEY,
|
|
147
|
-
user_id INTEGER NOT NULL,
|
|
148
|
-
token VARCHAR(255) NOT NULL,
|
|
149
|
-
expires_at TIMESTAMP NOT NULL,
|
|
150
|
-
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
151
|
-
)
|
|
152
|
-
`);else await db.unsafe(`
|
|
153
|
-
CREATE TABLE IF NOT EXISTS email_verifications (
|
|
154
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
155
|
-
user_id INTEGER NOT NULL,
|
|
156
|
-
token VARCHAR(255) NOT NULL,
|
|
157
|
-
expires_at TIMESTAMP NOT NULL,
|
|
158
|
-
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
159
|
-
)
|
|
160
|
-
`);await db.unsafe(`
|
|
161
|
-
CREATE UNIQUE INDEX IF NOT EXISTS idx_email_verifications_user_id_unique ON email_verifications(user_id)
|
|
162
|
-
`);try{await db.unsafe(usersEmailVerifiedAtSql(sql))}catch{}try{await db.unsafe(usersPasswordChangedAtSql(sql))}catch{}log.success("Email verifications table ready")}catch(error){await log.error("Failed to create email_verifications table",error);process.exit(1)}log.info("Ensuring webauthn_challenges table exists...");try{if(isPostgres)await db.unsafe(`
|
|
163
|
-
CREATE TABLE IF NOT EXISTS webauthn_challenges (
|
|
164
|
-
id SERIAL PRIMARY KEY,
|
|
165
|
-
user_id INTEGER NOT NULL,
|
|
166
|
-
challenge TEXT NOT NULL,
|
|
167
|
-
purpose VARCHAR(32) NOT NULL,
|
|
168
|
-
expires_at TIMESTAMP NOT NULL,
|
|
169
|
-
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
170
|
-
)
|
|
171
|
-
`);else if(isMysql)await db.unsafe(`
|
|
172
|
-
CREATE TABLE IF NOT EXISTS webauthn_challenges (
|
|
173
|
-
id INTEGER AUTO_INCREMENT PRIMARY KEY,
|
|
174
|
-
user_id INTEGER NOT NULL,
|
|
175
|
-
challenge TEXT NOT NULL,
|
|
176
|
-
purpose VARCHAR(32) NOT NULL,
|
|
177
|
-
expires_at TIMESTAMP NOT NULL,
|
|
178
|
-
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
179
|
-
)
|
|
180
|
-
`);else await db.unsafe(`
|
|
181
|
-
CREATE TABLE IF NOT EXISTS webauthn_challenges (
|
|
182
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
183
|
-
user_id INTEGER NOT NULL,
|
|
184
|
-
challenge TEXT NOT NULL,
|
|
185
|
-
purpose VARCHAR(32) NOT NULL,
|
|
186
|
-
expires_at TIMESTAMP NOT NULL,
|
|
187
|
-
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
188
|
-
)
|
|
189
|
-
`);await db.unsafe(`
|
|
190
|
-
CREATE UNIQUE INDEX IF NOT EXISTS idx_webauthn_challenges_user_purpose ON webauthn_challenges(user_id, purpose)
|
|
191
|
-
`);log.success("WebAuthn challenges table ready")}catch(error){await log.error("Failed to create webauthn_challenges table",error);process.exit(1)}log.info("Creating personal access client...");try{if((await db.unsafe(`
|
|
192
|
-
SELECT id FROM oauth_clients WHERE personal_access_client = ${boolTrue} LIMIT 1
|
|
193
|
-
`))?.length>0)console.log(`
|
|
194
|
-
\u2713 Personal access client already exists`);else{const secret=randomBytes(40).toString("hex"),hashedSecret=await makeHash(secret,{algorithm:"bcrypt"});if(isPostgres)await db.unsafe(`
|
|
195
|
-
INSERT INTO oauth_clients (name, secret, provider, redirect, personal_access_client, password_client, revoked, created_at)
|
|
196
|
-
VALUES ($1, $2, $3, $4, $5, $6, $7, NOW())
|
|
197
|
-
`,["Personal Access Client",hashedSecret,"local","http://localhost",!0,!1,!1]);else await db.unsafe(`
|
|
198
|
-
INSERT INTO oauth_clients (name, secret, provider, redirect, personal_access_client, password_client, revoked, created_at)
|
|
199
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ${now})
|
|
200
|
-
`,["Personal Access Client",hashedSecret,"local","http://localhost",1,0,0]);console.log(`
|
|
201
|
-
\u2713 Personal access client created successfully`)}}catch(error){console.error("Failed to create personal access client:",error);process.exit(1)}console.log(`
|
|
1
|
+
import process from"node:process";import{ensureDatabaseConfigLoaded,migrateAuthTables}from"@stacksjs/database";import{log}from"@stacksjs/logging";log.info("Setting up authentication...");try{await ensureDatabaseConfigLoaded();const result=await migrateAuthTables({verbose:!0});if(!result.success)throw Error(result.error||"Authentication tables could not be initialized")}catch(error){await log.error("Authentication setup failed",error);process.exit(1)}console.log(`
|
|
202
2
|
\u2713 Authentication setup complete!`);console.log(`
|
|
203
3
|
Features enabled:`);console.log(" - Token hashing (SHA-256) for secure storage");console.log(" - Refresh tokens for seamless token renewal");console.log(" - Scope-based authorization");console.log(" - Password reset flow");console.log(`
|
|
204
4
|
Next steps:`);console.log(" 1. Use createToken(userId, name, scopes) to generate tokens");console.log(" 2. Use refreshToken(token) to exchange refresh tokens");console.log(' 3. Use the "auth" middleware to protect routes');console.log(" 4. Use tokenCan(scope) for authorization checks");console.log(" 5. POST /password/forgot to request password reset");console.log(` 6. POST /password/reset to reset password with token
|
package/dist/bump.js
CHANGED
|
@@ -2,7 +2,7 @@ import{execSync,log,parseOptions}from"@stacksjs/cli";import{path as p}from"@stac
|
|
|
2
2
|
`).slice(0,4).join(`
|
|
3
3
|
`)))content=content.replace(/^(#\s.*\n+)?/,(match)=>`${match??""}## v${nextVersion}
|
|
4
4
|
|
|
5
|
-
`);writeFileSync(changelogPath,content)}await writeChangelog();if(!isDryRun&&isFrameworkRelease)pinLockstepDeps(nextVersion);async function refreshPantryLock(){const lockPath=p.projectPath("pantry.lock");if(!existsSync(lockPath))return;const previousLock=readFileSync(lockPath);try{await execSync(["pantry","install","--ignore-scripts","--quiet"],{cwd:p.projectPath(),stdin:"inherit"});if(!existsSync(lockPath))throw Error("Pantry completed without writing pantry.lock")}catch(error){writeFileSync(lockPath,previousLock);throw Error("Release aborted: Pantry could not refresh pantry.lock without lifecycle scripts. The previous lockfile was restored.",{cause:error})}}
|
|
5
|
+
`);writeFileSync(changelogPath,content)}await writeChangelog();if(!isDryRun&&isFrameworkRelease)pinLockstepDeps(nextVersion);async function refreshPantryLock(){const lockPath=p.projectPath("pantry.lock");if(!existsSync(lockPath))return;const previousLock=readFileSync(lockPath);try{await execSync(["pantry","install","--ignore-scripts","--quiet"],{cwd:p.projectPath(),stdin:"inherit"});if(!existsSync(lockPath))throw Error("Pantry completed without writing pantry.lock")}catch(error){writeFileSync(lockPath,previousLock);throw Error("Release aborted: Pantry could not refresh pantry.lock without lifecycle scripts. The previous lockfile was restored.",{cause:error})}}const bunLockPath=p.projectPath("bun.lock"),pantryLockPath=p.projectPath("pantry.lock"),committedBunLock=existsSync(bunLockPath)?readFileSync(bunLockPath):null,committedPantryLock=existsSync(pantryLockPath)?readFileSync(pantryLockPath):null;function restoreCommittedLockfiles(){if(committedBunLock)writeFileSync(bunLockPath,committedBunLock);if(committedPantryLock)writeFileSync(pantryLockPath,committedPantryLock)}if(!isDryRun)await refreshPantryLock();if(!isDryRun&&committedBunLock){const lockPath=bunLockPath,previousLock=committedBunLock;unlinkSync(lockPath);try{await execSync(["bun","install","--lockfile-only"],{cwd:p.projectPath(),stdin:"inherit"})}catch(error){restoreCommittedLockfiles();throw error}if(!existsSync(lockPath)){restoreCommittedLockfiles();throw Error("Release aborted: `bun install --lockfile-only` wrote no lockfile, so the previous one has been restored. This usually means a dependency range this bump just raised names a version that is not published yet - release that package first, then re-run.")}const expectedLockfileVersion=lockfileVersion(previousLock.toString("utf8")),regeneratedLock=readFileSync(lockPath,"utf8"),producedVersion=lockfileVersion(regeneratedLock);if(expectedLockfileVersion==null||producedVersion!==expectedLockfileVersion){restoreCommittedLockfiles();const remedy=producedVersion!=null&&expectedLockfileVersion!=null&&producedVersion>expectedLockfileVersion?"the committed lockfile predates the Bun this repository declares. Regenerate it once with that toolchain (`bun install --lockfile-only`) and commit the result as its own change, then re-run the release.":"this machine's Bun is older than the one that wrote the committed lockfile. Re-run the release with the repository's declared Bun toolchain through `pantry install`.";throw Error(`Release aborted: regenerating bun.lock produced lockfileVersion ${producedVersion??"unknown"}, but the committed one is v${expectedLockfileVersion??"unknown"} \u2014 ${remedy}`)}}function lockstepPackages(version){const names=new Set;for(const entry of readdirSync(p.frameworkPath("core"),{withFileTypes:!0})){if(!entry.isDirectory())continue;const manifest=p.frameworkPath(`core/${entry.name}/package.json`);if(!existsSync(manifest))continue;const pkg=JSON.parse(readFileSync(manifest,"utf-8"));if(pkg.name?.startsWith("@stacksjs/")&&pkg.version===version)names.add(pkg.name)}return names}function pinLockstepDeps(version){const lockstep=lockstepPackages(version),next=`^${version}`,manifests=[p.frameworkPath("core/package.json"),...readdirSync(p.frameworkPath("core"),{withFileTypes:!0}).filter((entry)=>entry.isDirectory()).map((entry)=>p.frameworkPath(`core/${entry.name}/package.json`)).filter((path)=>existsSync(path))];let pinned=0;for(const manifestPath of manifests){const manifest=JSON.parse(readFileSync(manifestPath,"utf-8"));let changed=!1;for(const field of["dependencies","devDependencies"]){const deps=manifest[field];if(!deps)continue;for(const name of Object.keys(deps)){if(!lockstep.has(name)||deps[name].startsWith("workspace:")||deps[name]===next)continue;deps[name]=next;changed=!0;pinned++}}if(changed)writeFileSync(manifestPath,`${JSON.stringify(manifest,null,2)}
|
|
6
6
|
`)}if(pinned>0)log.debug(`Pinned ${pinned} lockstep core dep(s) to ^${version}`)}async function stageReleaseArtifacts(){const pathspecs=isFrameworkRelease?[":(glob)storage/framework/**/package.json","package.json"]:["package.json"];for(const file of["CHANGELOG.md","bun.lock","pantry.lock"]){if(!existsSync(p.projectPath(file)))continue;if(await isGitIgnored(file))continue;pathspecs.push(file)}await git(["add","--",...pathspecs]);const leftover=(await git(["status","--porcelain"])).split(`
|
|
7
7
|
`).map((line)=>line.trimEnd()).filter((line)=>line&&!line.startsWith("A ")&&!line.startsWith("M "));if(leftover.length)log.warn(`Kept ${leftover.length} change(s) out of the release commit:
|
|
8
8
|
${leftover.slice(0,20).join(`
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export declare function resolveDashboardProxyStrategy(dashboardDomain: string | null, probes: DashboardProxyProbes): Promise<DashboardProxyStrategy>;
|
|
2
|
+
export declare interface DashboardProxyProbes {
|
|
3
|
+
proxyManagedExternally: boolean
|
|
4
|
+
isDaemonRunning: () => Promise<boolean>
|
|
5
|
+
httpsPortAnswers: () => Promise<boolean>
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* How `dashboard.<domain>` gets served in development.
|
|
9
|
+
*
|
|
10
|
+
* - `parent-managed`: `buddy dev` started us and already owns rpx, TLS and the
|
|
11
|
+
* :443 daemon. A second proxy here would fight it for the port and break
|
|
12
|
+
* routing for the other hosts (app, docs, api).
|
|
13
|
+
* - `daemon-route`: a shared rpx daemon is already serving :443, so publishing a
|
|
14
|
+
* registry route is both sufficient and all an unprivileged process can do.
|
|
15
|
+
* Starting an in-process proxy instead could not bind :443, and rpx would
|
|
16
|
+
* settle on some high port that the pretty URL never reaches.
|
|
17
|
+
* - `own-proxy`: nothing holds :443, so this process runs the proxy itself.
|
|
18
|
+
* - `origin-only`: no custom domain configured; `http://localhost:<port>` is the
|
|
19
|
+
* only URL there is.
|
|
20
|
+
*/
|
|
21
|
+
export type DashboardProxyStrategy = 'parent-managed' | 'daemon-route' | 'own-proxy' | 'origin-only';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export async function resolveDashboardProxyStrategy(dashboardDomain,probes){if(!dashboardDomain)return"origin-only";if(probes.proxyManagedExternally)return"parent-managed";if(await probes.isDaemonRunning()&&await probes.httpsPortAnswers())return"daemon-route";return"own-proxy"}
|
|
@@ -13,7 +13,21 @@ export declare function buildManifest(models: DiscoveredModel[]): DiscoveredMode
|
|
|
13
13
|
export declare function isPortAvailable(port: number): Promise<boolean>;
|
|
14
14
|
/** Find an available port starting from the preferred port */
|
|
15
15
|
export declare function findAvailablePort(preferred: number, maxAttempts?: number): Promise<number>;
|
|
16
|
-
/**
|
|
16
|
+
/**
|
|
17
|
+
* Wait for a dev server to accept connections, for at most `maxWait` ms.
|
|
18
|
+
*
|
|
19
|
+
* The probe is a TCP connect, which is the question actually being asked. It
|
|
20
|
+
* used to be `HEAD /`, and on the dashboard that means rendering the index
|
|
21
|
+
* page - roughly eight seconds of STX and Crosswind compilation on a cold
|
|
22
|
+
* boot. Worse, the budget was only re-checked between attempts, so one slow
|
|
23
|
+
* response ran unbounded: `maxWait` of 500ms routinely took 11 seconds, all of
|
|
24
|
+
* it before the banner printed, with nothing on screen to explain the wait.
|
|
25
|
+
*
|
|
26
|
+
* Nothing is lost by not rendering here: the STX server's own
|
|
27
|
+
* `prewarmRenderCache` compiles the index in the background either way, so the
|
|
28
|
+
* page is warm by the time anyone opens it - it just no longer holds the
|
|
29
|
+
* banner hostage while it does.
|
|
30
|
+
*/
|
|
17
31
|
export declare function waitForServer(port: number, maxWait?: number): Promise<boolean>;
|
|
18
32
|
/** Build sidebar config for native sidebar navigation */
|
|
19
33
|
export declare function buildSidebarConfig(baseRoute: string, discoveredModels: DiscoveredModel[], toggles?: DashboardSectionToggles): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{Glob}from"bun";import{existsSync}from"node:fs";import path from"node:path";import{pathToFileURL}from"node:url";export const iconMap={user:"person.fill",team:"person.2.fill",subscriber:"envelope.open.fill",subscriberemail:"envelope.fill",post:"doc.text.fill",page:"doc.fill",product:"cube.box.fill",productvariant:"cube.fill",productunit:"scalemass.fill",order:"list.clipboard.fill",orderitem:"list.bullet.clipboard.fill",customer:"person.crop.circle.fill",payment:"creditcard.fill",paymentmethod:"creditcard.and.123",paymentproduct:"creditcard.fill",paymenttransaction:"creditcard.fill",category:"tag.fill",tag:"tag",comment:"bubble.left.fill",author:"person.text.rectangle.fill",notification:"bell.fill",job:"briefcase.fill",log:"list.bullet.rectangle.portrait.fill",campaign:"megaphone.fill",review:"star.fill",coupon:"ticket.fill",giftcard:"giftcard.fill",cart:"cart.fill",cartitem:"cart.badge.plus",taxrate:"percent",transaction:"arrow.left.arrow.right.circle.fill",subscription:"repeat.circle.fill",emaillist:"envelope.badge.person.crop",socialpost:"bubble.left.and.bubble.right.fill",manufacturer:"building.2.fill",shippingmethod:"shippingbox.fill",shippingzone:"map.fill",shippingrate:"dollarsign.circle.fill",deliveryroute:"map.fill",driver:"car.fill",digitaldelivery:"arrow.down.doc.fill",licensekey:"key.fill",loyaltypoint:"star.circle.fill",loyaltyreward:"gift.fill",printdevice:"printer.fill",receipt:"doc.text.fill",waitlistproduct:"clock.fill",waitlistrestaurant:"clock.badge.checkmark.fill",websocket:"antenna.radiowaves.left.and.right",default:"tablecells.fill"},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"]);const ROOT_MODEL_CATEGORY={user:"data",team:"data",subscriber:"data",activity:"data",campaign:"marketing",emaillist:"marketing",socialpost:"marketing",subscriberemail:"marketing",comment:"content",tag:"content",paymentmethod:"commerce",paymentproduct:"commerce",paymenttransaction:"commerce",subscription:"commerce",deployment:"system",error:"system",failedjob:"system",job:"system",log:"system",notification:"system",release:"system",request:"system"};export function modelNameToId(name){return name.replace(/([A-Z])/g,"-$1").toLowerCase().replace(/^-/,"")}export function getModelIcon(name){return iconMap[name.toLowerCase()]||iconMap.default||"database"}function categorizeModel(relativePath,modelId,defaultCategory){if(defaultCategory)return defaultCategory;const firstSeg=relativePath.includes("/")?(relativePath.split("/")[0]??"").toLowerCase():"";if(firstSeg==="commerce")return"commerce";if(firstSeg==="content")return"content";if(firstSeg==="realtime")return"system";return ROOT_MODEL_CATEGORY[modelId.replace(/-/g,"")]??"data"}async function readModelDashboardConfig(absolutePath){try{const mod=await import(pathToFileURL(absolutePath).href);return(mod.default??mod).dashboard}catch(error){throw Error(`Could not discover dashboard model ${absolutePath}: ${error instanceof Error?error.message:String(error)}`)}}export async function scanModelsDir(dir,seenNames,models,defaultCategory){if(!existsSync(dir))return;const glob=new Glob("**/*.ts");for await(const file of glob.scan({cwd:dir})){if(file.includes("README")||file.includes(".d.ts"))continue;const name=file.replace(/\.ts$/,"").split("/").pop()||"",nameLower=name.toLowerCase();if(name&&!seenNames.has(nameLower)&&!excludeModels.has(nameLower)){seenNames.add(nameLower);const id=modelNameToId(name),absolutePath=path.resolve(dir,file),dashboard=await readModelDashboardConfig(absolutePath);models.push({name,icon:dashboard?.icon??getModelIcon(name),id,category:categorizeModel(file,id,defaultCategory),dashboard})}}}export async function discoverModels(userModelsPath,defaultModelsPath){const models=[],seenNames=new Set;await scanModelsDir(userModelsPath,seenNames,models,"userland");await scanModelsDir(defaultModelsPath,seenNames,models);return models.sort((a,b)=>a.name.localeCompare(b.name))}export function buildManifest(models){return models.map((m)=>{const row={id:m.id,name:m.name,icon:m.icon,hasDedicatedPage:dedicatedPages.has(m.id),category:m.category??"data"};if(m.dashboard)row.dashboard=m.dashboard;return row})}export async function isPortAvailable(port){try{await Bun.serve({port,fetch:()=>new Response}).stop(!0);return!0}catch{return!1}}export async function findAvailablePort(preferred,maxAttempts=20){for(let i=0;i<maxAttempts;i++){const port=preferred+i;if(await isPortAvailable(port))return port}return preferred}export async function waitForServer(port,maxWait=500){const start=Date.now();while(Date.now()-start<maxWait){try{const res=await fetch(`http://localhost:${port}/`,{method:"HEAD"});if(res.ok||res.status===404)return!0}catch{}await new Promise((resolve)=>setTimeout(resolve,25))}return!1}function effectiveCategory(m){if(m.dashboard?.enabled===!1)return;if(m.dashboard?.section)return m.dashboard.section;return m.category??"data"}function categoryRows(baseRoute,models,category,dedicated){return models.filter((m)=>effectiveCategory(m)===category).filter((m)=>!dedicatedPages.has(m.id)&&!dedicated.has(m.id)).map((m)=>({id:`model-${m.id}`,label:m.dashboard?.label??m.name,icon:m.icon,url:`${baseRoute}/models/${m.id}`,...m.dashboard?.roles&&m.dashboard.roles.length>0?{roles:m.dashboard.roles}:{}}))}export function buildSidebarConfig(baseRoute,discoveredModels,toggles={}){const enabled={library:toggles.library??!0,content:toggles.content??!0,commerce:toggles.commerce??!0,marketing:toggles.marketing??!0,analytics:toggles.analytics??!0,management:toggles.management??!0,utilities:toggles.utilities??!0},dataRow={dashboard:toggles.data?.dashboard??!0,activity:toggles.data?.activity??!0,users:toggles.data?.users??!0,teams:toggles.data?.teams??!0,subscribers:toggles.data?.subscribers??!0,allModels:toggles.data?.allModels??!0},sections=[],homeItems=[{id:"home",label:"Dashboard",icon:"house.fill",url:`${baseRoute}/`},{id:"dependencies",label:"Dependencies",icon:"shippingbox.fill",url:`${baseRoute}/dependencies`},{id:"health",label:"Health",icon:"heart.text.square.fill",url:`${baseRoute}/health`}];if(toggles.ci)homeItems.push({id:"ci",label:"CI",icon:"checkmark.seal.fill",url:`${baseRoute}/ci`});sections.push({id:"home",title:"Home",items:homeItems});if(enabled.library)sections.push({id:"library",title:"Library",items:[{id:"functions",label:"Functions",icon:"function",url:`${baseRoute}/functions`},{id:"packages",label:"Packages",icon:"shippingbox.fill",url:`${baseRoute}/packages`}]});if(enabled.content)sections.push({id:"content",title:"Content",items:[{id:"content-dashboard",label:"Dashboard",icon:"gauge.with.dots.needle.33percent",url:`${baseRoute}/content/dashboard`},{id:"files",label:"Files",icon:"folder.fill",url:`${baseRoute}/content/files`},{id:"pages",label:"Pages",icon:"doc.fill",url:`${baseRoute}/content/pages`},{id:"posts",label:"Posts",icon:"text.bubble.fill",url:`${baseRoute}/content/posts`},{id:"categories",label:"Categories",icon:"tag.fill",url:`${baseRoute}/content/categories`},{id:"tags",label:"Tags",icon:"tag",url:`${baseRoute}/content/tags`},{id:"comments",label:"Comments",icon:"bubble.left.fill",url:`${baseRoute}/content/comments`},{id:"authors",label:"Authors",icon:"person.text.rectangle.fill",url:`${baseRoute}/content/authors`},{id:"seo",label:"SEO",icon:"magnifyingglass",url:`${baseRoute}/content/seo`},...categoryRows(baseRoute,discoveredModels,"content",new Set(["page","post","category","tag","comment","author"]))]});sections.push({id:"app",title:"App",items:[{id:"deployments",label:"Deployments",icon:"rocket.fill",url:`${baseRoute}/deployments`},{id:"requests",label:"Requests",icon:"arrow.left.arrow.right",url:`${baseRoute}/requests`},{id:"realtime",label:"Realtime",icon:"link",url:`${baseRoute}/realtime`},{id:"actions",label:"Actions",icon:"bolt.fill",url:`${baseRoute}/actions`},{id:"commands",label:"Commands",icon:"terminal.fill",url:`${baseRoute}/commands`},{id:"queue",label:"Queue",icon:"list.bullet.rectangle.fill",url:`${baseRoute}/queue`},{id:"jobs",label:"Jobs",icon:"briefcase.fill",url:`${baseRoute}/jobs`},{id:"inbox",label:"Inbox",icon:"tray.full.fill",url:`${baseRoute}/inbox`},{id:"queries",label:"Queries",icon:"magnifyingglass.circle.fill",url:`${baseRoute}/queries`},{id:"notifications",label:"Notifications",icon:"bell.fill",url:`${baseRoute}/notifications/dashboard`},...categoryRows(baseRoute,discoveredModels,"system",new Set)]});const dataItems=[];if(dataRow.dashboard)dataItems.push({id:"data-dashboard",label:"Dashboard",icon:"gauge.with.dots.needle.33percent",url:`${baseRoute}/data/dashboard`});if(dataRow.activity)dataItems.push({id:"activity",label:"Activity",icon:"waveform.path.ecg",url:`${baseRoute}/data/activity`});if(dataRow.users)dataItems.push({id:"users",label:"Users",icon:"person.crop.circle.fill",url:`${baseRoute}/data/users`});if(dataRow.teams)dataItems.push({id:"teams",label:"Teams",icon:"person.3.fill",url:`${baseRoute}/data/teams`});if(dataRow.subscribers)dataItems.push({id:"subscribers",label:"Subscribers",icon:"envelope.fill",url:`${baseRoute}/data/subscribers`});if(dataRow.allModels)dataItems.push({id:"data-models",label:"All Models",icon:"square.grid.2x2.fill",url:`${baseRoute}/models`});const dynamicRows=discoveredModels.filter((m)=>{const eff=effectiveCategory(m);return eff==="userland"||eff==="data"}).filter((m)=>!dedicatedPages.has(m.id)).map((model)=>({id:`model-${model.id}`,label:model.dashboard?.label??model.name,icon:model.icon,url:`${baseRoute}/models/${model.id}`,...model.dashboard?.roles&&model.dashboard.roles.length>0?{roles:model.dashboard.roles}:{}}));sections.push({id:"data",title:"Data",items:[...dataItems,...dynamicRows]});if(enabled.commerce)sections.push({id:"commerce",title:"Commerce",items:[{id:"commerce-dashboard",label:"Dashboard",icon:"gauge.with.dots.needle.33percent",url:`${baseRoute}/commerce/dashboard`},{id:"customers",label:"Customers",icon:"person.crop.circle.fill",url:`${baseRoute}/commerce/customers`},{id:"orders",label:"Orders",icon:"list.clipboard.fill",url:`${baseRoute}/commerce/orders`},{id:"products",label:"Products",icon:"cube.box.fill",url:`${baseRoute}/commerce/products`},{id:"coupons",label:"Coupons",icon:"ticket.fill",url:`${baseRoute}/commerce/coupons`},{id:"gift-cards",label:"Gift Cards",icon:"giftcard.fill",url:`${baseRoute}/commerce/gift-cards`},{id:"payments",label:"Payments",icon:"creditcard.fill",url:`${baseRoute}/commerce/payments`},{id:"delivery",label:"Delivery",icon:"truck.box.fill",url:`${baseRoute}/commerce/delivery`},{id:"taxes",label:"Taxes",icon:"percent",url:`${baseRoute}/commerce/taxes`},...categoryRows(baseRoute,discoveredModels,"commerce",new Set(["customer","order","product","coupon","gift-card","payment","review","category"]))]});if(enabled.marketing)sections.push({id:"marketing",title:"Marketing",items:[{id:"lists",label:"Lists",icon:"list.bullet",url:`${baseRoute}/marketing/lists`},{id:"social-posts",label:"Social Posts",icon:"clock.fill",url:`${baseRoute}/marketing/social-posts`},{id:"campaigns",label:"Campaigns",icon:"megaphone.fill",url:`${baseRoute}/marketing/campaigns`},{id:"marketing-reviews",label:"Reviews",icon:"star.fill",url:`${baseRoute}/marketing/reviews`},...categoryRows(baseRoute,discoveredModels,"marketing",new Set(["campaign"]))]});if(enabled.analytics)sections.push({id:"analytics",title:"Analytics",items:[{id:"analytics-web",label:"Web",icon:"globe",url:`${baseRoute}/analytics/web`},{id:"analytics-blog",label:"Blog",icon:"doc.text.fill",url:`${baseRoute}/analytics/blog`},{id:"analytics-commerce",label:"Commerce",icon:"cart.fill",url:`${baseRoute}/analytics/commerce/web`},{id:"analytics-marketing",label:"Marketing",icon:"megaphone.fill",url:`${baseRoute}/analytics/marketing`}]});if(enabled.management)sections.push({id:"management",title:"Management",items:[{id:"cloud",label:"Cloud",icon:"cloud.fill",url:`${baseRoute}/cloud`},{id:"servers",label:"Servers",icon:"server.rack",url:`${baseRoute}/servers`},{id:"serverless",label:"Serverless",icon:"bolt.horizontal.circle.fill",url:`${baseRoute}/serverless`},{id:"dns",label:"DNS",icon:"network",url:`${baseRoute}/dns`},{id:"mailboxes",label:"Mailboxes",icon:"tray.full.fill",url:`${baseRoute}/mailboxes`},{id:"logs",label:"Logs",icon:"list.bullet.rectangle.portrait.fill",url:`${baseRoute}/logs`},{id:"kanban",label:"Kanban",icon:"rectangle.stack.fill",url:`${baseRoute}/kanban`,roles:["admin","dev"]}]});if(enabled.utilities)sections.push({id:"utilities",title:"Utilities",items:[{id:"buddy",label:"AI Buddy",icon:"bubble.left.and.bubble.right.fill",url:`${baseRoute}/buddy`},{id:"environment",label:"Environment",icon:"key.fill",url:`${baseRoute}/environment`},{id:"access-tokens",label:"Access Tokens",icon:"key.horizontal.fill",url:`${baseRoute}/access-tokens`},{id:"settings",label:"Settings",icon:"gear",url:`${baseRoute}/settings/billing`}]});return{sections,minWidth:200,maxWidth:280}}
|
|
1
|
+
import{Glob}from"bun";import{existsSync}from"node:fs";import{connect}from"node:net";import path from"node:path";import{pathToFileURL}from"node:url";export const iconMap={user:"person.fill",team:"person.2.fill",subscriber:"envelope.open.fill",subscriberemail:"envelope.fill",post:"doc.text.fill",page:"doc.fill",product:"cube.box.fill",productvariant:"cube.fill",productunit:"scalemass.fill",order:"list.clipboard.fill",orderitem:"list.bullet.clipboard.fill",customer:"person.crop.circle.fill",payment:"creditcard.fill",paymentmethod:"creditcard.and.123",paymentproduct:"creditcard.fill",paymenttransaction:"creditcard.fill",category:"tag.fill",tag:"tag",comment:"bubble.left.fill",author:"person.text.rectangle.fill",notification:"bell.fill",job:"briefcase.fill",log:"list.bullet.rectangle.portrait.fill",campaign:"megaphone.fill",review:"star.fill",coupon:"ticket.fill",giftcard:"giftcard.fill",cart:"cart.fill",cartitem:"cart.badge.plus",taxrate:"percent",transaction:"arrow.left.arrow.right.circle.fill",subscription:"repeat.circle.fill",emaillist:"envelope.badge.person.crop",socialpost:"bubble.left.and.bubble.right.fill",manufacturer:"building.2.fill",shippingmethod:"shippingbox.fill",shippingzone:"map.fill",shippingrate:"dollarsign.circle.fill",deliveryroute:"map.fill",driver:"car.fill",digitaldelivery:"arrow.down.doc.fill",licensekey:"key.fill",loyaltypoint:"star.circle.fill",loyaltyreward:"gift.fill",printdevice:"printer.fill",receipt:"doc.text.fill",waitlistproduct:"clock.fill",waitlistrestaurant:"clock.badge.checkmark.fill",websocket:"antenna.radiowaves.left.and.right",default:"tablecells.fill"},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"]);const ROOT_MODEL_CATEGORY={user:"data",team:"data",subscriber:"data",activity:"data",campaign:"marketing",emaillist:"marketing",socialpost:"marketing",subscriberemail:"marketing",comment:"content",tag:"content",paymentmethod:"commerce",paymentproduct:"commerce",paymenttransaction:"commerce",subscription:"commerce",deployment:"system",error:"system",failedjob:"system",job:"system",log:"system",notification:"system",release:"system",request:"system"};export function modelNameToId(name){return name.replace(/([A-Z])/g,"-$1").toLowerCase().replace(/^-/,"")}export function getModelIcon(name){return iconMap[name.toLowerCase()]||iconMap.default||"database"}function categorizeModel(relativePath,modelId,defaultCategory){if(defaultCategory)return defaultCategory;const firstSeg=relativePath.includes("/")?(relativePath.split("/")[0]??"").toLowerCase():"";if(firstSeg==="commerce")return"commerce";if(firstSeg==="content")return"content";if(firstSeg==="realtime")return"system";return ROOT_MODEL_CATEGORY[modelId.replace(/-/g,"")]??"data"}async function readModelDashboardConfig(absolutePath){try{const mod=await import(pathToFileURL(absolutePath).href);return(mod.default??mod).dashboard}catch(error){throw Error(`Could not discover dashboard model ${absolutePath}: ${error instanceof Error?error.message:String(error)}`)}}export async function scanModelsDir(dir,seenNames,models,defaultCategory){if(!existsSync(dir))return;const glob=new Glob("**/*.ts");for await(const file of glob.scan({cwd:dir})){if(file.includes("README")||file.includes(".d.ts"))continue;const name=file.replace(/\.ts$/,"").split("/").pop()||"",nameLower=name.toLowerCase();if(name&&!seenNames.has(nameLower)&&!excludeModels.has(nameLower)){seenNames.add(nameLower);const id=modelNameToId(name),absolutePath=path.resolve(dir,file),dashboard=await readModelDashboardConfig(absolutePath);models.push({name,icon:dashboard?.icon??getModelIcon(name),id,category:categorizeModel(file,id,defaultCategory),dashboard})}}}export async function discoverModels(userModelsPath,defaultModelsPath){const models=[],seenNames=new Set;await scanModelsDir(userModelsPath,seenNames,models,"userland");await scanModelsDir(defaultModelsPath,seenNames,models);return models.sort((a,b)=>a.name.localeCompare(b.name))}export function buildManifest(models){return models.map((m)=>{const row={id:m.id,name:m.name,icon:m.icon,hasDedicatedPage:dedicatedPages.has(m.id),category:m.category??"data"};if(m.dashboard)row.dashboard=m.dashboard;return row})}export async function isPortAvailable(port){try{await Bun.serve({port,fetch:()=>new Response}).stop(!0);return!0}catch{return!1}}export async function findAvailablePort(preferred,maxAttempts=20){for(let i=0;i<maxAttempts;i++){const port=preferred+i;if(await isPortAvailable(port))return port}return preferred}function connectsWithin(port,timeoutMs){return new Promise((resolve)=>{const socket=connect({port,host:"127.0.0.1"}),finish=(connected)=>{clearTimeout(timer);try{socket.destroy()}catch{}resolve(connected)},timer=setTimeout(()=>finish(!1),timeoutMs);socket.once("connect",()=>finish(!0));socket.once("error",()=>finish(!1))})}export async function waitForServer(port,maxWait=500){const deadline=Date.now()+maxWait;while(!0){const remaining=deadline-Date.now();if(remaining<=0)return!1;if(await connectsWithin(port,Math.min(remaining,250)))return!0;if(Date.now()>=deadline)return!1;await new Promise((resolve)=>setTimeout(resolve,Math.min(25,Math.max(deadline-Date.now(),0))))}}function effectiveCategory(m){if(m.dashboard?.enabled===!1)return;if(m.dashboard?.section)return m.dashboard.section;return m.category??"data"}function categoryRows(baseRoute,models,category,dedicated){return models.filter((m)=>effectiveCategory(m)===category).filter((m)=>!dedicatedPages.has(m.id)&&!dedicated.has(m.id)).map((m)=>({id:`model-${m.id}`,label:m.dashboard?.label??m.name,icon:m.icon,url:`${baseRoute}/models/${m.id}`,...m.dashboard?.roles&&m.dashboard.roles.length>0?{roles:m.dashboard.roles}:{}}))}export function buildSidebarConfig(baseRoute,discoveredModels,toggles={}){const enabled={library:toggles.library??!0,content:toggles.content??!0,commerce:toggles.commerce??!0,marketing:toggles.marketing??!0,analytics:toggles.analytics??!0,management:toggles.management??!0,utilities:toggles.utilities??!0},dataRow={dashboard:toggles.data?.dashboard??!0,activity:toggles.data?.activity??!0,users:toggles.data?.users??!0,teams:toggles.data?.teams??!0,subscribers:toggles.data?.subscribers??!0,allModels:toggles.data?.allModels??!0},sections=[],homeItems=[{id:"home",label:"Dashboard",icon:"house.fill",url:`${baseRoute}/`},{id:"dependencies",label:"Dependencies",icon:"shippingbox.fill",url:`${baseRoute}/dependencies`},{id:"health",label:"Health",icon:"heart.text.square.fill",url:`${baseRoute}/health`}];if(toggles.ci)homeItems.push({id:"ci",label:"CI",icon:"checkmark.seal.fill",url:`${baseRoute}/ci`});sections.push({id:"home",title:"Home",items:homeItems});if(enabled.library)sections.push({id:"library",title:"Library",items:[{id:"functions",label:"Functions",icon:"function",url:`${baseRoute}/functions`},{id:"packages",label:"Packages",icon:"shippingbox.fill",url:`${baseRoute}/packages`}]});if(enabled.content)sections.push({id:"content",title:"Content",items:[{id:"content-dashboard",label:"Dashboard",icon:"gauge.with.dots.needle.33percent",url:`${baseRoute}/content/dashboard`},{id:"files",label:"Files",icon:"folder.fill",url:`${baseRoute}/content/files`},{id:"pages",label:"Pages",icon:"doc.fill",url:`${baseRoute}/content/pages`},{id:"posts",label:"Posts",icon:"text.bubble.fill",url:`${baseRoute}/content/posts`},{id:"categories",label:"Categories",icon:"tag.fill",url:`${baseRoute}/content/categories`},{id:"tags",label:"Tags",icon:"tag",url:`${baseRoute}/content/tags`},{id:"comments",label:"Comments",icon:"bubble.left.fill",url:`${baseRoute}/content/comments`},{id:"authors",label:"Authors",icon:"person.text.rectangle.fill",url:`${baseRoute}/content/authors`},{id:"seo",label:"SEO",icon:"magnifyingglass",url:`${baseRoute}/content/seo`},...categoryRows(baseRoute,discoveredModels,"content",new Set(["page","post","category","tag","comment","author"]))]});sections.push({id:"app",title:"App",items:[{id:"deployments",label:"Deployments",icon:"rocket.fill",url:`${baseRoute}/deployments`},{id:"requests",label:"Requests",icon:"arrow.left.arrow.right",url:`${baseRoute}/requests`},{id:"realtime",label:"Realtime",icon:"link",url:`${baseRoute}/realtime`},{id:"actions",label:"Actions",icon:"bolt.fill",url:`${baseRoute}/actions`},{id:"commands",label:"Commands",icon:"terminal.fill",url:`${baseRoute}/commands`},{id:"queue",label:"Queue",icon:"list.bullet.rectangle.fill",url:`${baseRoute}/queue`},{id:"jobs",label:"Jobs",icon:"briefcase.fill",url:`${baseRoute}/jobs`},{id:"inbox",label:"Inbox",icon:"tray.full.fill",url:`${baseRoute}/inbox`},{id:"queries",label:"Queries",icon:"magnifyingglass.circle.fill",url:`${baseRoute}/queries`},{id:"notifications",label:"Notifications",icon:"bell.fill",url:`${baseRoute}/notifications/dashboard`},...categoryRows(baseRoute,discoveredModels,"system",new Set)]});const dataItems=[];if(dataRow.dashboard)dataItems.push({id:"data-dashboard",label:"Dashboard",icon:"gauge.with.dots.needle.33percent",url:`${baseRoute}/data/dashboard`});if(dataRow.activity)dataItems.push({id:"activity",label:"Activity",icon:"waveform.path.ecg",url:`${baseRoute}/data/activity`});if(dataRow.users)dataItems.push({id:"users",label:"Users",icon:"person.crop.circle.fill",url:`${baseRoute}/data/users`});if(dataRow.teams)dataItems.push({id:"teams",label:"Teams",icon:"person.3.fill",url:`${baseRoute}/data/teams`});if(dataRow.subscribers)dataItems.push({id:"subscribers",label:"Subscribers",icon:"envelope.fill",url:`${baseRoute}/data/subscribers`});if(dataRow.allModels)dataItems.push({id:"data-models",label:"All Models",icon:"square.grid.2x2.fill",url:`${baseRoute}/models`});const dynamicRows=discoveredModels.filter((m)=>{const eff=effectiveCategory(m);return eff==="userland"||eff==="data"}).filter((m)=>!dedicatedPages.has(m.id)).map((model)=>({id:`model-${model.id}`,label:model.dashboard?.label??model.name,icon:model.icon,url:`${baseRoute}/models/${model.id}`,...model.dashboard?.roles&&model.dashboard.roles.length>0?{roles:model.dashboard.roles}:{}}));sections.push({id:"data",title:"Data",items:[...dataItems,...dynamicRows]});if(enabled.commerce)sections.push({id:"commerce",title:"Commerce",items:[{id:"commerce-dashboard",label:"Dashboard",icon:"gauge.with.dots.needle.33percent",url:`${baseRoute}/commerce/dashboard`},{id:"customers",label:"Customers",icon:"person.crop.circle.fill",url:`${baseRoute}/commerce/customers`},{id:"orders",label:"Orders",icon:"list.clipboard.fill",url:`${baseRoute}/commerce/orders`},{id:"products",label:"Products",icon:"cube.box.fill",url:`${baseRoute}/commerce/products`},{id:"coupons",label:"Coupons",icon:"ticket.fill",url:`${baseRoute}/commerce/coupons`},{id:"gift-cards",label:"Gift Cards",icon:"giftcard.fill",url:`${baseRoute}/commerce/gift-cards`},{id:"payments",label:"Payments",icon:"creditcard.fill",url:`${baseRoute}/commerce/payments`},{id:"delivery",label:"Delivery",icon:"truck.box.fill",url:`${baseRoute}/commerce/delivery`},{id:"taxes",label:"Taxes",icon:"percent",url:`${baseRoute}/commerce/taxes`},...categoryRows(baseRoute,discoveredModels,"commerce",new Set(["customer","order","product","coupon","gift-card","payment","review","category"]))]});if(enabled.marketing)sections.push({id:"marketing",title:"Marketing",items:[{id:"lists",label:"Lists",icon:"list.bullet",url:`${baseRoute}/marketing/lists`},{id:"social-posts",label:"Social Posts",icon:"clock.fill",url:`${baseRoute}/marketing/social-posts`},{id:"campaigns",label:"Campaigns",icon:"megaphone.fill",url:`${baseRoute}/marketing/campaigns`},{id:"marketing-reviews",label:"Reviews",icon:"star.fill",url:`${baseRoute}/marketing/reviews`},...categoryRows(baseRoute,discoveredModels,"marketing",new Set(["campaign"]))]});if(enabled.analytics)sections.push({id:"analytics",title:"Analytics",items:[{id:"analytics-web",label:"Web",icon:"globe",url:`${baseRoute}/analytics/web`},{id:"analytics-blog",label:"Blog",icon:"doc.text.fill",url:`${baseRoute}/analytics/blog`},{id:"analytics-commerce",label:"Commerce",icon:"cart.fill",url:`${baseRoute}/analytics/commerce/web`},{id:"analytics-marketing",label:"Marketing",icon:"megaphone.fill",url:`${baseRoute}/analytics/marketing`}]});if(enabled.management)sections.push({id:"management",title:"Management",items:[{id:"cloud",label:"Cloud",icon:"cloud.fill",url:`${baseRoute}/cloud`},{id:"servers",label:"Servers",icon:"server.rack",url:`${baseRoute}/servers`},{id:"serverless",label:"Serverless",icon:"bolt.horizontal.circle.fill",url:`${baseRoute}/serverless`},{id:"dns",label:"DNS",icon:"network",url:`${baseRoute}/dns`},{id:"mailboxes",label:"Mailboxes",icon:"tray.full.fill",url:`${baseRoute}/mailboxes`},{id:"logs",label:"Logs",icon:"list.bullet.rectangle.portrait.fill",url:`${baseRoute}/logs`},{id:"kanban",label:"Kanban",icon:"rectangle.stack.fill",url:`${baseRoute}/kanban`,roles:["admin","dev"]}]});if(enabled.utilities)sections.push({id:"utilities",title:"Utilities",items:[{id:"buddy",label:"AI Buddy",icon:"bubble.left.and.bubble.right.fill",url:`${baseRoute}/buddy`},{id:"environment",label:"Environment",icon:"key.fill",url:`${baseRoute}/environment`},{id:"access-tokens",label:"Access Tokens",icon:"key.horizontal.fill",url:`${baseRoute}/access-tokens`},{id:"settings",label:"Settings",icon:"gear",url:`${baseRoute}/settings/billing`}]});return{sections,minWidth:200,maxWidth:280}}
|
package/dist/dev/dashboard.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import process from"node:process";import{randomUUID}from"node:crypto";import{existsSync,renameSync,unlinkSync,writeFileSync}from"node:fs";import{bold,cyan,dim,green}from"@stacksjs/cli";import{projectPath,publicPath,storagePath}from"@stacksjs/path";import{seedCsrfPageResponse,validateDevCsrfRequest}from"./csrf";import{shouldDelegateDashboardRequest}from"./dashboard-request-routing";import{resolveDashboardCraftExecutable}from"./dashboard-native";import{buildManifest,discoverModels,findAvailablePort,waitForServer}from"./dashboard-utils";import{runDashboardSupervisor}from"./dashboard-supervisor";if(process.env.STACKS_DASHBOARD_WORKER!=="1"){const exitCode=await runDashboardSupervisor(import.meta.path,process.argv.slice(2));process.exit(exitCode)}const verboseIdx=process.argv.indexOf("--verbose"),verbose=verboseIdx!==-1&&process.argv[verboseIdx+1]!=="false",startTime=Bun.nanoseconds(),{log:originalConsoleLog,warn:originalConsoleWarn}=console,bufferedLogs=[];console.log=(...args)=>{bufferedLogs.push(args.map(String).join(" "))};console.warn=(...args)=>{bufferedLogs.push(args.map(String).join(" "))};const dashboardPath=storagePath("framework/defaults/views/dashboard"),userDashboardPath=projectPath("resources/views/dashboard"),preferredPort=Number(process.env.PORT_ADMIN)||3002,dashboardPort=await findAvailablePort(preferredPort),appUrl=process.env.APP_URL||"",hasCustomDomain=appUrl!==""&&appUrl!=="localhost"&&!appUrl.includes("localhost:"),domain=hasCustomDomain?appUrl.replace(/^https?:\/\//,""):null,dashboardDomain=domain?`dashboard.${domain}`:null,sslBasePath=`${process.env.HOME}/.stacks/ssl`;function restoreConsole(){console.log=originalConsoleLog;console.warn=(...args)=>{const msg=args.map(String).join(" ");if(msg.includes("[STX] DOM API violation")||msg.includes("unsafe expression"))return;originalConsoleWarn(...args)}}async function startStxServer(){await import("@stacksjs/orm");let serve;try{const localServe=process.env.BUN_PLUGIN_STX_SRC||`${process.env.HOME}/Code/Tools/stx/packages/bun-plugin/src/serve.ts`;if(!await Bun.file(localServe).exists())throw Error("local bun-plugin-stx source not found");serve=(await import(localServe)).serve}catch{try{serve=(await import("bun-plugin-stx/serve")).serve}catch{serve=(await import(projectPath("pantry/bun-plugin-stx/dist/serve.js"))).serve}}let stxModule;try{const localStx=process.env.STX_SRC||`${process.env.HOME}/Code/Tools/stx/packages/stx/src/index.ts`;if(await Bun.file(localStx).exists()){const localCrosswind=`${process.env.HOME}/Code/Tools/crosswind/packages/crosswind/src/index.ts`;if(!process.env.CROSSWIND_SRC&&await Bun.file(localCrosswind).exists())process.env.CROSSWIND_SRC=localCrosswind;stxModule=await import(localStx)}}catch{}if(stxModule&&verbose)console.log("[Dashboard] Using local stx checkout");if(!stxModule)try{const vendoredStx=projectPath("pantry/@stacksjs/stx/dist/index.js");if(await Bun.file(vendoredStx).exists())stxModule=await import(vendoredStx)}catch{}const{listConfigFiles,readConfig,updateConfigKeys}=await import(storagePath("framework/defaults/resources/functions/dashboard/config-io.ts")),localTsChartsRoot=process.env.TS_CHARTS_SRC||`${process.env.HOME}/Code/Libraries/ts-charts`,dependencyBundles=new Map;function resolveTsChartsSource(packageName){return[`${localTsChartsRoot}/packages/${packageName}/src/index.ts`,projectPath(`node_modules/@ts-charts/${packageName}/src/index.ts`),projectPath(`pantry/@ts-charts/${packageName}/src/index.ts`)].find(existsSync)||null}const tsChartsEntry=[`${localTsChartsRoot}/packages/ts-charts/src/index.ts`,projectPath("node_modules/ts-charts/src/index.ts"),projectPath("pantry/ts-charts/src/index.ts")].find(existsSync);async function buildDashboardDependency(entrypoint){const cached=dependencyBundles.get(entrypoint);if(cached)return cached;const build=(async()=>{const result=await Bun.build({entrypoints:[entrypoint],target:"browser",format:"esm",minify:!0,plugins:[{name:"dashboard-ts-charts-source",setup(builder){builder.onResolve({filter:/^@ts-charts\/[a-z0-9-]+$/},(args)=>{const packageName=args.path.slice(11),source=resolveTsChartsSource(packageName);if(source)return{path:source};return null})}}]});if(!result.success){const errors=result.logs.filter((log)=>log.level==="error").map((log)=>log.message).join(`
|
|
2
|
-
`);throw Error(errors||`Failed to bundle ${entrypoint}`)}const output=result.outputs.find((file)=>file.path.endsWith(".js"));if(!output)throw Error(`No JavaScript output produced for ${entrypoint}`);return output.text()})();dependencyBundles.set(entrypoint,build);return build}function browserDependencyRoute(entrypoint){return async()=>{try{return new Response(await buildDashboardDependency(entrypoint),{headers:{"content-type":"text/javascript; charset=utf-8","cache-control":"no-cache"}})}catch(error){dependencyBundles.delete(entrypoint);return new Response(`throw new Error(${JSON.stringify(error.message)})`,{status:500,headers:{"content-type":"text/javascript; charset=utf-8"}})}}}let dashboardIconCss=null;async function buildDashboardIconCss(){const{generateCss}=await import("@stacksjs/stx"),dirs=[dashboardPath,userDashboardPath,storagePath("framework/defaults/resources/components/Dashboard")],icons=new Set;for(const dir of dirs){if(!existsSync(dir))continue;const glob=new Bun.Glob("**/*.{stx,ts}");for await(const file of glob.scan({cwd:dir,absolute:!0})){const text=await Bun.file(file).text();for(const match of text.matchAll(/\bi-[a-z][a-z0-9]*(?:-[a-z0-9]+)+/g))icons.add(match[0])}}if(icons.size===0)return"";return generateCss(`<div class="${[...icons].join(" ")}"></div>`,process.cwd())}const configRoutes={"/__deps/charts.js":browserDependencyRoute(storagePath("framework/core/charts/src/index.ts")),"/__deps/ts-charts.js":browserDependencyRoute(tsChartsEntry||`${localTsChartsRoot}/packages/ts-charts/src/index.ts`),"/__deps/dashboard-icons.css":async()=>{if(!dashboardIconCss)dashboardIconCss=buildDashboardIconCss();return new Response(await dashboardIconCss,{headers:{"content-type":"text/css; charset=utf-8","cache-control":"no-cache"}})},"/api/config/list":async()=>{try{return Response.json({ok:!0,files:listConfigFiles()})}catch(e){return Response.json({ok:!1,error:e?.message},{status:500})}},"/api/config/read":async(req)=>{try{const name=new URL(req.url).searchParams.get("name")||"";if(!/^[\w-]+$/.test(name))return Response.json({ok:!1,error:"Invalid config name"},{status:400});const result=await readConfig(name);if(!result)return Response.json({ok:!1,error:"Not found"},{status:404});return Response.json({ok:!0,name,fields:result.fields})}catch(e){return Response.json({ok:!1,error:e?.message},{status:500})}},"/api/config/source":async(req)=>{try{const name=new URL(req.url).searchParams.get("name")||"";if(!/^[\w-]+$/.test(name))return Response.json({ok:!1,error:"Invalid config name"},{status:400});const result=await readConfig(name);if(!result)return Response.json({ok:!1,error:"Not found"},{status:404});return new Response(result.source,{headers:{"content-type":"text/plain; charset=utf-8"}})}catch(e){return Response.json({ok:!1,error:e?.message},{status:500})}},"/api/config/update":async(req)=>{if(req.method!=="POST")return Response.json({ok:!1,error:"Method not allowed"},{status:405});try{await validateDevCsrfRequest(req);const body=await req.json(),file=body.file?.replace(/\.ts$/,"");if(!file||!/^[\w-]+$/.test(file))return Response.json({ok:!1,error:"Invalid file"},{status:400});const updates=[];if(Array.isArray(body.updates))for(const u of body.updates){const k=u.key??u.path;if(typeof k==="string")updates.push({key:k,value:u.value})}else if(body.key)updates.push({key:body.key,value:body.value});if(updates.length===0)return Response.json({ok:!1,error:"No updates supplied"},{status:400});const invalid=updates.find((update)=>!/^[A-Za-z_$][\w$]*$/.test(update.key)||typeof update.value!=="string"&&typeof update.value!=="number"&&typeof update.value!=="boolean"||typeof update.value==="number"&&!Number.isFinite(update.value));if(invalid)return Response.json({ok:!1,error:`Invalid value for configuration key "${invalid.key}"`},{status:400});const typedUpdates=updates;try{await updateConfigKeys(file,typedUpdates)}catch(err){const error=err?.message||"Configuration values could not be updated.",results=typedUpdates.map((update)=>({key:update.key,ok:!1,error}));return Response.json({ok:!1,file,results,error},{status:422})}const results=typedUpdates.map((update)=>({key:update.key,ok:!0,newValue:update.value}));return Response.json({ok:!0,file,results})}catch(e){if(e?.status===403)return Response.json({ok:!1,error:"Forbidden",message:"CSRF token mismatch"},{status:403});return Response.json({ok:!1,error:e?.message},{status:500})}}},router=await import("@stacksjs/router"),routeRegistry=(await import(projectPath("app/Routes.ts"))).default;await router.loadRoutes(routeRegistry);const stacksRoute=router.route;serve({root:projectPath("resources"),patterns:[userDashboardPath,dashboardPath],port:dashboardPort,componentsDir:storagePath("framework/defaults/resources/components/Dashboard"),layoutsDir:dashboardPath,partialsDir:dashboardPath,publicDir:publicPath(),renderCache:!0,renderCacheVary:"source",prewarmRenderCache:4,watchDirs:[projectPath("resources/functions"),storagePath("framework/defaults/resources/functions")],quiet:!0,routes:configRoutes,onRequest:async(req)=>{const pathname=new URL(req.url).pathname;if(shouldDelegateDashboardRequest(pathname,req.method))return stacksRoute.handleRequest(req);return null},onResponse:seedCsrfPageResponse,auth:!1,...stxModule&&{stxModule}}).catch((err)=>{restoreConsole();console.error(`
|
|
1
|
+
import process from"node:process";import{randomUUID}from"node:crypto";import{existsSync,renameSync,unlinkSync,writeFileSync}from"node:fs";import{connect}from"node:net";import{bold,cyan,dim,green}from"@stacksjs/cli";import{projectPath,publicPath,storagePath}from"@stacksjs/path";import{seedCsrfPageResponse,validateDevCsrfRequest}from"./csrf";import{resolveComponentsLibraryRoot}from"./defaults-resources";import{shouldDelegateDashboardRequest}from"./dashboard-request-routing";import{resolveDashboardCraftExecutable}from"./dashboard-native";import{resolveDashboardProxyStrategy}from"./dashboard-proxy";import{buildManifest,discoverModels,findAvailablePort,waitForServer}from"./dashboard-utils";import{runDashboardSupervisor}from"./dashboard-supervisor";if(process.env.STACKS_DASHBOARD_WORKER!=="1"){const exitCode=await runDashboardSupervisor(import.meta.path,process.argv.slice(2));process.exit(exitCode)}const verboseIdx=process.argv.indexOf("--verbose"),verbose=verboseIdx!==-1&&process.argv[verboseIdx+1]!=="false",startTime=Bun.nanoseconds(),{log:originalConsoleLog,warn:originalConsoleWarn}=console,bufferedLogs=[];console.log=(...args)=>{bufferedLogs.push(args.map(String).join(" "))};console.warn=(...args)=>{bufferedLogs.push(args.map(String).join(" "))};const dashboardPath=storagePath("framework/defaults/views/dashboard"),userDashboardPath=projectPath("resources/views/dashboard"),preferredPort=Number(process.env.PORT_ADMIN)||3002,dashboardPort=await findAvailablePort(preferredPort),appUrl=process.env.APP_URL||"",hasCustomDomain=appUrl!==""&&appUrl!=="localhost"&&!appUrl.includes("localhost:"),domain=hasCustomDomain?appUrl.replace(/^https?:\/\//,""):null,dashboardDomain=domain?`dashboard.${domain}`:null,sslBasePath=`${process.env.HOME}/.stacks/ssl`;function restoreConsole(){console.log=originalConsoleLog;console.warn=(...args)=>{const msg=args.map(String).join(" ");if(msg.includes("[STX] DOM API violation")||msg.includes("unsafe expression"))return;originalConsoleWarn(...args)}}async function startStxServer(){await import("@stacksjs/orm");let serve;try{const localServe=process.env.BUN_PLUGIN_STX_SRC||`${process.env.HOME}/Code/Tools/stx/packages/bun-plugin/src/serve.ts`;if(!await Bun.file(localServe).exists())throw Error("local bun-plugin-stx source not found");serve=(await import(localServe)).serve}catch{try{serve=(await import("bun-plugin-stx/serve")).serve}catch{serve=(await import(projectPath("pantry/bun-plugin-stx/dist/serve.js"))).serve}}let stxModule;try{const localStx=process.env.STX_SRC||`${process.env.HOME}/Code/Tools/stx/packages/stx/src/index.ts`;if(await Bun.file(localStx).exists()){const localCrosswind=`${process.env.HOME}/Code/Tools/crosswind/packages/crosswind/src/index.ts`;if(!process.env.CROSSWIND_SRC&&await Bun.file(localCrosswind).exists())process.env.CROSSWIND_SRC=localCrosswind;stxModule=await import(localStx)}}catch{}if(stxModule&&verbose)console.log("[Dashboard] Using local stx checkout");if(!stxModule)try{const vendoredStx=projectPath("pantry/@stacksjs/stx/dist/index.js");if(await Bun.file(vendoredStx).exists())stxModule=await import(vendoredStx)}catch{}const{listConfigFiles,readConfig,updateConfigKeys}=await import(storagePath("framework/defaults/resources/functions/dashboard/config-io.ts")),localTsChartsRoot=process.env.TS_CHARTS_SRC||`${process.env.HOME}/Code/Libraries/ts-charts`,dependencyBundles=new Map;function resolveTsChartsSource(packageName){return[`${localTsChartsRoot}/packages/${packageName}/src/index.ts`,projectPath(`node_modules/@ts-charts/${packageName}/src/index.ts`),projectPath(`pantry/@ts-charts/${packageName}/src/index.ts`)].find(existsSync)||null}const tsChartsEntry=[`${localTsChartsRoot}/packages/ts-charts/src/index.ts`,projectPath("node_modules/ts-charts/src/index.ts"),projectPath("pantry/ts-charts/src/index.ts")].find(existsSync);async function buildDashboardDependency(entrypoint){const cached=dependencyBundles.get(entrypoint);if(cached)return cached;const build=(async()=>{const result=await Bun.build({entrypoints:[entrypoint],target:"browser",format:"esm",minify:!0,plugins:[{name:"dashboard-ts-charts-source",setup(builder){builder.onResolve({filter:/^@ts-charts\/[a-z0-9-]+$/},(args)=>{const packageName=args.path.slice(11),source=resolveTsChartsSource(packageName);if(source)return{path:source};return null})}}]});if(!result.success){const errors=result.logs.filter((log)=>log.level==="error").map((log)=>log.message).join(`
|
|
2
|
+
`);throw Error(errors||`Failed to bundle ${entrypoint}`)}const output=result.outputs.find((file)=>file.path.endsWith(".js"));if(!output)throw Error(`No JavaScript output produced for ${entrypoint}`);return output.text()})();dependencyBundles.set(entrypoint,build);return build}function browserDependencyRoute(entrypoint){return async()=>{try{return new Response(await buildDashboardDependency(entrypoint),{headers:{"content-type":"text/javascript; charset=utf-8","cache-control":"no-cache"}})}catch(error){dependencyBundles.delete(entrypoint);return new Response(`throw new Error(${JSON.stringify(error.message)})`,{status:500,headers:{"content-type":"text/javascript; charset=utf-8"}})}}}let dashboardIconCss=null;async function buildDashboardIconCss(){const{generateCss}=await import("@stacksjs/stx"),dirs=[dashboardPath,userDashboardPath,storagePath("framework/defaults/resources/components/Dashboard")],icons=new Set;for(const dir of dirs){if(!existsSync(dir))continue;const glob=new Bun.Glob("**/*.{stx,ts}");for await(const file of glob.scan({cwd:dir,absolute:!0})){const text=await Bun.file(file).text();for(const match of text.matchAll(/\bi-[a-z][a-z0-9]*(?:-[a-z0-9]+)+/g))icons.add(match[0])}}if(icons.size===0)return"";return generateCss(`<div class="${[...icons].join(" ")}"></div>`,process.cwd())}const configRoutes={"/__deps/charts.js":browserDependencyRoute(storagePath("framework/core/charts/src/index.ts")),"/__deps/ts-charts.js":browserDependencyRoute(tsChartsEntry||`${localTsChartsRoot}/packages/ts-charts/src/index.ts`),"/__deps/dashboard-icons.css":async()=>{if(!dashboardIconCss)dashboardIconCss=buildDashboardIconCss();return new Response(await dashboardIconCss,{headers:{"content-type":"text/css; charset=utf-8","cache-control":"no-cache"}})},"/api/config/list":async()=>{try{return Response.json({ok:!0,files:listConfigFiles()})}catch(e){return Response.json({ok:!1,error:e?.message},{status:500})}},"/api/config/read":async(req)=>{try{const name=new URL(req.url).searchParams.get("name")||"";if(!/^[\w-]+$/.test(name))return Response.json({ok:!1,error:"Invalid config name"},{status:400});const result=await readConfig(name);if(!result)return Response.json({ok:!1,error:"Not found"},{status:404});return Response.json({ok:!0,name,fields:result.fields})}catch(e){return Response.json({ok:!1,error:e?.message},{status:500})}},"/api/config/source":async(req)=>{try{const name=new URL(req.url).searchParams.get("name")||"";if(!/^[\w-]+$/.test(name))return Response.json({ok:!1,error:"Invalid config name"},{status:400});const result=await readConfig(name);if(!result)return Response.json({ok:!1,error:"Not found"},{status:404});return new Response(result.source,{headers:{"content-type":"text/plain; charset=utf-8"}})}catch(e){return Response.json({ok:!1,error:e?.message},{status:500})}},"/api/config/update":async(req)=>{if(req.method!=="POST")return Response.json({ok:!1,error:"Method not allowed"},{status:405});try{await validateDevCsrfRequest(req);const body=await req.json(),file=body.file?.replace(/\.ts$/,"");if(!file||!/^[\w-]+$/.test(file))return Response.json({ok:!1,error:"Invalid file"},{status:400});const updates=[];if(Array.isArray(body.updates))for(const u of body.updates){const k=u.key??u.path;if(typeof k==="string")updates.push({key:k,value:u.value})}else if(body.key)updates.push({key:body.key,value:body.value});if(updates.length===0)return Response.json({ok:!1,error:"No updates supplied"},{status:400});const invalid=updates.find((update)=>!/^[A-Za-z_$][\w$]*$/.test(update.key)||typeof update.value!=="string"&&typeof update.value!=="number"&&typeof update.value!=="boolean"||typeof update.value==="number"&&!Number.isFinite(update.value));if(invalid)return Response.json({ok:!1,error:`Invalid value for configuration key "${invalid.key}"`},{status:400});const typedUpdates=updates;try{await updateConfigKeys(file,typedUpdates)}catch(err){const error=err?.message||"Configuration values could not be updated.",results=typedUpdates.map((update)=>({key:update.key,ok:!1,error}));return Response.json({ok:!1,file,results,error},{status:422})}const results=typedUpdates.map((update)=>({key:update.key,ok:!0,newValue:update.value}));return Response.json({ok:!0,file,results})}catch(e){if(e?.status===403)return Response.json({ok:!1,error:"Forbidden",message:"CSRF token mismatch"},{status:403});return Response.json({ok:!1,error:e?.message},{status:500})}}},router=await import("@stacksjs/router"),routeRegistry=(await import(projectPath("app/Routes.ts"))).default;await router.loadRoutes(routeRegistry);const stacksRoute=router.route;serve({root:projectPath("resources"),patterns:[userDashboardPath,dashboardPath],port:dashboardPort,componentsDir:storagePath("framework/defaults/resources/components/Dashboard"),fallbackComponentsDir:resolveComponentsLibraryRoot(),layoutsDir:dashboardPath,partialsDir:dashboardPath,publicDir:publicPath(),renderCache:!0,renderCacheVary:"source",prewarmRenderCache:4,watchDirs:[projectPath("resources/functions"),storagePath("framework/defaults/resources/functions")],quiet:!0,routes:configRoutes,onRequest:async(req)=>{const pathname=new URL(req.url).pathname;if(shouldDelegateDashboardRequest(pathname,req.method))return stacksRoute.handleRequest(req);return null},onResponse:seedCsrfPageResponse,auth:!1,...stxModule&&{stxModule}}).catch((err)=>{restoreConsole();console.error(`
|
|
3
3
|
Failed to start dashboard server: ${err?.message||err}
|
|
4
|
-
`);process.exit(1)})}
|
|
4
|
+
`);process.exit(1)})}function portAnswers(port,timeoutMs=800){return new Promise((resolve)=>{const socket=connect({port,host:"127.0.0.1"}),finish=(answered)=>{try{socket.destroy()}catch{}resolve(answered)};socket.once("connect",()=>finish(!0));socket.once("error",()=>finish(!1));setTimeout(()=>finish(!1),timeoutMs).unref?.()})}async function registerWithRpxDaemon(domainToServe){const{writeEntry}=await import("@stacksjs/rpx");await writeEntry({id:`${domainToServe}-dashboard`,from:`localhost:${dashboardPort}`,to:domainToServe,cwd:process.cwd(),createdAt:new Date().toISOString(),pid:process.pid},void 0,verbose);return!0}async function startReverseProxy(){if(!dashboardDomain)return!1;try{let rpx;const loadRpx=async()=>rpx??=await import("@stacksjs/rpx"),strategy=await resolveDashboardProxyStrategy(dashboardDomain,{proxyManagedExternally:Boolean(process.env.STACKS_PROXY_MANAGED),isDaemonRunning:async()=>(await loadRpx()).isDaemonRunning(),httpsPortAnswers:()=>portAnswers(443)});if(strategy==="origin-only")return!1;if(strategy==="parent-managed")return!0;if(strategy==="daemon-route")return await registerWithRpxDaemon(dashboardDomain);await(await loadRpx()).startProxies({proxies:[{from:`localhost:${dashboardPort}`,to:dashboardDomain,cleanUrls:!1}],https:{basePath:sslBasePath,validityDays:825},regenerateUntrustedCerts:!1,verbose});return await portAnswers(443)}catch(error){if(verbose)originalConsoleLog(` ${dim(`Proxy: ${error}`)}`);return!1}}const[,discoveredModels]=await Promise.all([startStxServer(),discoverModels(projectPath("app/Models"),storagePath("framework/defaults/app/Models"))]),{loadDashboardConfig}=await import(storagePath("framework/defaults/resources/functions/dashboard/toggles.ts")),{toggles:dashboardToggles,nav:dashboardNav}=await loadDashboardConfig(projectPath("config/dashboard.ts")),manifestPath=storagePath("framework/defaults/views/dashboard/.discovered-models.json"),manifestPayload={models:buildManifest(discoveredModels),sections:dashboardToggles,nav:dashboardNav},manifestTempPath=`${manifestPath}.${process.pid}.${randomUUID()}.tmp`;try{writeFileSync(manifestTempPath,JSON.stringify(manifestPayload,null,2),{flag:"wx"});renameSync(manifestTempPath,manifestPath)}finally{if(existsSync(manifestTempPath))unlinkSync(manifestTempPath)}const serverReady=await waitForServer(dashboardPort);restoreConsole();const proxyStarted=await startReverseProxy().catch((err)=>{if(verbose)console.warn("[Dashboard] Reverse proxy failed:",err);return!1}),dashboardHttpsUrl=dashboardDomain?`https://${dashboardDomain}`:null,dashboardLocalUrl=`http://localhost:${dashboardPort}`,initialUrl=dashboardHttpsUrl&&proxyStarted?`${dashboardHttpsUrl}/`:`${dashboardLocalUrl}/`,elapsedMs=(Bun.nanoseconds()-startTime)/1e6;console.log();console.log(` ${bold(cyan("stacks dashboard"))}`);console.log();if(dashboardHttpsUrl&&proxyStarted){console.log(` ${green("\u279C")} ${bold("Local")}: ${cyan(dashboardHttpsUrl)}`);console.log(` ${dim("\u279C")} ${dim("Origin")}: ${dim(dashboardLocalUrl)}`)}else{console.log(` ${green("\u279C")} ${bold("Local")}: ${cyan(dashboardLocalUrl)}`);if(dashboardHttpsUrl)console.log(` ${dim("\u279C")} ${dim("Pretty")}: ${dim(`${dashboardHttpsUrl} needs the rpx proxy - run \`buddy dev\``)}`)}console.log(` ${green("\u279C")} ${bold("Window")}: ${dim("Stacks Dashboard")} ${dim("1400\xD7900")}`);console.log(` ${green("\u279C")} ${bold("Models")}: ${dim(`${discoveredModels.length} discovered`)}`);if(dashboardPort!==preferredPort)console.log(` ${dim("\u279C")} ${dim(`Port ${preferredPort} in use, using ${dashboardPort}`)}`);if(!serverReady)console.log(` ${dim("\u26A0")} ${dim("Dev server may not be ready yet")}`);console.log();console.log(` ${dim(`ready in ${elapsedMs.toFixed(0)} ms`)}`);if(verbose){console.log();console.log(` ${dim("\u279C")} ${dim("URL")}: ${dim(initialUrl)}`);if(dashboardDomain){console.log(` ${dim("\u279C")} ${dim("SSL")}: ${dim(sslBasePath)}`);console.log(` ${dim("\u279C")} ${dim("Proxy")}: ${dim(`localhost:${dashboardPort} \u2192 ${dashboardDomain}`)}`)}if(bufferedLogs.length>0){console.log();for(const line of bufferedLogs)console.log(` ${dim(line)}`)}}console.log();let createApp,resolveCraftBinary;const nativeDisabled=process.env.STACKS_NO_NATIVE==="1",explicitCraftSdk=process.env.CRAFT_SDK_SRC;if(!nativeDisabled&&explicitCraftSdk){if(!existsSync(explicitCraftSdk))throw Error(`CRAFT_SDK_SRC not found: ${explicitCraftSdk}`);try{({createApp,resolveCraftBinary}=await import(explicitCraftSdk));if(!createApp||!resolveCraftBinary)throw Error("CRAFT_SDK_SRC must export createApp and resolveCraftBinary")}catch(error){throw Error(`Could not load CRAFT_SDK_SRC ${explicitCraftSdk}: ${error instanceof Error?error.message:String(error)}`)}}if(!nativeDisabled&&!explicitCraftSdk)try{({createApp,resolveCraftBinary}=await import("craft-native"))}catch{}const craftBinaryPath=createApp&&resolveCraftBinary?resolveDashboardCraftExecutable(resolveCraftBinary):void 0;if(createApp&&craftBinaryPath){const userIconPath=projectPath("resources/assets/images/app-icon.png"),defaultIconPath=storagePath("framework/defaults/resources/assets/images/app-icon.png"),appIconPath=await Bun.file(userIconPath).exists()?userIconPath:await Bun.file(defaultIconPath).exists()?defaultIconPath:void 0,app=createApp({url:dashboardLocalUrl,quiet:!verbose,craftPath:craftBinaryPath,window:{title:"Stacks Dashboard",width:1400,height:900,titlebarHidden:!0,webSidebarMaterial:!0,webSidebarWidth:250,...appIconPath&&{icon:appIconPath}}});process.on("SIGINT",()=>{app.close();process.exit(0)});process.on("SIGTERM",()=>{app.close();process.exit(0)});try{await app.show();process.exit(0)}catch(error){const fallbackUrl=dashboardHttpsUrl||dashboardLocalUrl;console.log(` ${dim("Native Craft window failed:")} ${dim(error instanceof Error?error.message:String(error))}`);console.log(` ${dim("Dashboard available at:")} ${cyan(fallbackUrl)}
|
|
5
5
|
`);await new Promise(()=>{})}}else{console.log(` ${dim(nativeDisabled?"Native window disabled by STACKS_NO_NATIVE=1. Open the URL above in a browser.":"Native window unavailable. Run pantry install craft, set CRAFT_BIN, or open the URL above in a browser.")}
|
|
6
6
|
`);process.on("SIGINT",()=>process.exit(0));process.on("SIGTERM",()=>process.exit(0));await new Promise(()=>{})}
|
|
@@ -22,4 +22,15 @@
|
|
|
22
22
|
* surface a clear missing-directory error rather than a silent empty glob.
|
|
23
23
|
*/
|
|
24
24
|
export declare function resolveDefaultsRoot(): string;
|
|
25
|
+
/*.stx` filenames carry a content hash
|
|
26
|
+
* (`Sidebar-f1tff5p3.stx`), so name-based component resolution never matches
|
|
27
|
+
* them. The package declares `['./src/ui', './src/components']` for its own stx
|
|
28
|
+
* plugin, and no `.stx` lives under `src` outside those two, so this single
|
|
29
|
+
* root covers both.
|
|
30
|
+
*
|
|
31
|
+
* Resolved relative to THIS module rather than the working directory: the dev
|
|
32
|
+
* server is started from wherever the developer happens to be, and the package
|
|
33
|
+
* may be hoisted anywhere above this file.
|
|
34
|
+
*/
|
|
35
|
+
export declare function resolveComponentsLibraryRoot(): string | undefined;
|
|
25
36
|
export declare function resolveDefaultsResources(): string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{existsSync}from"node:fs";import{dirname,join}from"node:path";function nearestContaining(start,marker){let dir=start;while(!0){const candidate=join(dir,marker);if(existsSync(candidate))return candidate;const parent=dirname(dir);if(parent===dir)return;dir=parent}}export function resolveDefaultsRoot(){const vendored=nearestContaining(import.meta.dir,"storage/framework/defaults")??nearestContaining(process.cwd(),"storage/framework/defaults");if(vendored)return vendored;try{const pkgJson=Bun.resolveSync("@stacksjs/defaults/package.json",process.cwd());return dirname(pkgJson)}catch{return join(process.cwd(),"storage/framework/defaults")}}export function resolveDefaultsResources(){const vendored=nearestContaining(import.meta.dir,"storage/framework/defaults/resources")??nearestContaining(process.cwd(),"storage/framework/defaults/resources");if(vendored)return vendored;try{const pkgJson=Bun.resolveSync("@stacksjs/defaults/package.json",process.cwd());return join(dirname(pkgJson),"resources")}catch{return join(process.cwd(),"storage/framework/defaults/resources")}}
|
|
1
|
+
import{existsSync}from"node:fs";import{dirname,join}from"node:path";function nearestContaining(start,marker){let dir=start;while(!0){const candidate=join(dir,marker);if(existsSync(candidate))return candidate;const parent=dirname(dir);if(parent===dir)return;dir=parent}}export function resolveDefaultsRoot(){const vendored=nearestContaining(import.meta.dir,"storage/framework/defaults")??nearestContaining(process.cwd(),"storage/framework/defaults");if(vendored)return vendored;try{const pkgJson=Bun.resolveSync("@stacksjs/defaults/package.json",process.cwd());return dirname(pkgJson)}catch{return join(process.cwd(),"storage/framework/defaults")}}export function resolveComponentsLibraryRoot(){try{return join(dirname(Bun.resolveSync("@stacksjs/components/stx-plugin",import.meta.dir)),"src")}catch{return}}export function resolveDefaultsResources(){const vendored=nearestContaining(import.meta.dir,"storage/framework/defaults/resources")??nearestContaining(process.cwd(),"storage/framework/defaults/resources");if(vendored)return vendored;try{const pkgJson=Bun.resolveSync("@stacksjs/defaults/package.json",process.cwd());return join(dirname(pkgJson),"resources")}catch{return join(process.cwd(),"storage/framework/defaults/resources")}}
|
package/dist/serve/dashboard.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{existsSync}from"node:fs";import{join}from"node:path";import process from"node:process";import{config}from"@stacksjs/config";import{log}from"@stacksjs/logging";import{projectPath,publicPath}from"@stacksjs/path";import{resolveDefaultsRoot}from"../dev/defaults-resources";import{decideDashboardAccess}from"./dashboard-gate";process.env.APP_ENV||="production";process.env.NODE_ENV||="production";const port=Number(process.env.PORT_DASHBOARD||process.env.PORT)||3002,defaultsRoot=resolveDefaultsRoot(),frameworkDashboard=join(defaultsRoot,"views/dashboard"),appDashboard=projectPath("resources/views/dashboard"),{authCookieName}=await import("@stacksjs/auth"),authCookie=authCookieName();function readCookie(header,name){if(!header)return;for(const part of header.split(";")){const raw=part.trim(),eq=raw.indexOf("=");if(eq<1)continue;if(raw.slice(0,eq)===name)return decodeURIComponent(raw.slice(eq+1))}return}const{isApiBoundRequest,proxyToBackend,resolveApiBase,resolveApiProxyRules}=await import("@stacksjs/server"),apiBase=resolveApiBase(config.ports?.api),apiProxyRules=resolveApiProxyRules(config.server?.proxy),{Auth}=await import("@stacksjs/auth"),{serve}=await import("bun-plugin-stx/serve");await serve({patterns:[appDashboard,frameworkDashboard].filter((dir)=>existsSync(dir)),port,layoutsDir:frameworkDashboard,partialsDir:frameworkDashboard,componentsDir:join(defaultsRoot,"resources/components/Dashboard"),publicDir:publicPath(),quiet:!0,onRequest:async(req)=>{const url=new URL(req.url);if((await decideDashboardAccess({method:req.method,pathname:url.pathname,token:readCookie(req.headers.get("cookie"),authCookie)},(token)=>Auth.getUserFromToken(token))).allow){if(isApiBoundRequest(req,url.pathname,apiProxyRules)){if(!apiBase){log.error(`No API target configured for ${url.pathname}. This dashboard shares its host with other deployments, so there is no safe default port to guess - refusing to proxy. Set PORT_API (or API_URL) for this site.`);return new Response("Bad Gateway",{status:502})}try{return await proxyToBackend(req,apiBase)}catch(error){log.error(`API proxy to ${apiBase} failed: ${error.message}`);return new Response("Bad Gateway",{status:502})}}return null}const next=encodeURIComponent(url.pathname+url.search);return new Response(null,{status:302,headers:{location:`/login?next=${next}`,"cache-control":"no-store, private"}})}});console.log(`Dashboard server listening on port ${port}`);
|
|
1
|
+
import{existsSync}from"node:fs";import{join}from"node:path";import process from"node:process";import{config}from"@stacksjs/config";import{log}from"@stacksjs/logging";import{projectPath,publicPath}from"@stacksjs/path";import{resolveComponentsLibraryRoot,resolveDefaultsRoot}from"../dev/defaults-resources";import{decideDashboardAccess}from"./dashboard-gate";process.env.APP_ENV||="production";process.env.NODE_ENV||="production";const port=Number(process.env.PORT_DASHBOARD||process.env.PORT)||3002,defaultsRoot=resolveDefaultsRoot(),frameworkDashboard=join(defaultsRoot,"views/dashboard"),appDashboard=projectPath("resources/views/dashboard"),{authCookieName}=await import("@stacksjs/auth"),authCookie=authCookieName();function readCookie(header,name){if(!header)return;for(const part of header.split(";")){const raw=part.trim(),eq=raw.indexOf("=");if(eq<1)continue;if(raw.slice(0,eq)===name)return decodeURIComponent(raw.slice(eq+1))}return}const{isApiBoundRequest,proxyToBackend,resolveApiBase,resolveApiProxyRules}=await import("@stacksjs/server"),apiBase=resolveApiBase(config.ports?.api),apiProxyRules=resolveApiProxyRules(config.server?.proxy),{Auth}=await import("@stacksjs/auth"),{serve}=await import("bun-plugin-stx/serve");await serve({patterns:[appDashboard,frameworkDashboard].filter((dir)=>existsSync(dir)),port,layoutsDir:frameworkDashboard,partialsDir:frameworkDashboard,componentsDir:join(defaultsRoot,"resources/components/Dashboard"),fallbackComponentsDir:resolveComponentsLibraryRoot(),publicDir:publicPath(),quiet:!0,onRequest:async(req)=>{const url=new URL(req.url);if((await decideDashboardAccess({method:req.method,pathname:url.pathname,token:readCookie(req.headers.get("cookie"),authCookie)},(token)=>Auth.getUserFromToken(token))).allow){if(isApiBoundRequest(req,url.pathname,apiProxyRules)){if(!apiBase){log.error(`No API target configured for ${url.pathname}. This dashboard shares its host with other deployments, so there is no safe default port to guess - refusing to proxy. Set PORT_API (or API_URL) for this site.`);return new Response("Bad Gateway",{status:502})}try{return await proxyToBackend(req,apiBase)}catch(error){log.error(`API proxy to ${apiBase} failed: ${error.message}`);return new Response("Bad Gateway",{status:502})}}return null}const next=encodeURIComponent(url.pathname+url.search);return new Response(null,{status:302,headers:{location:`/login?next=${next}`,"cache-control":"no-store, private"}})}});console.log(`Dashboard server listening on port ${port}`);
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@stacksjs/actions",
|
|
3
3
|
"type": "module",
|
|
4
4
|
"sideEffects": false,
|
|
5
|
-
"version": "0.74.
|
|
5
|
+
"version": "0.74.47",
|
|
6
6
|
"description": "The Stacks actions.",
|
|
7
7
|
"author": "Chris Breuer",
|
|
8
8
|
"contributors": [
|
|
@@ -60,51 +60,52 @@
|
|
|
60
60
|
"prepublishOnly": "bun run build"
|
|
61
61
|
},
|
|
62
62
|
"dependencies": {
|
|
63
|
-
"@stacksjs/action-runner": "0.74.
|
|
64
|
-
"@stacksjs/ai": "0.74.
|
|
65
|
-
"@stacksjs/analytics": "0.74.
|
|
66
|
-
"@stacksjs/api": "0.74.
|
|
67
|
-
"@stacksjs/auth": "0.74.
|
|
68
|
-
"@stacksjs/build": "0.74.
|
|
63
|
+
"@stacksjs/action-runner": "0.74.47",
|
|
64
|
+
"@stacksjs/ai": "0.74.47",
|
|
65
|
+
"@stacksjs/analytics": "0.74.47",
|
|
66
|
+
"@stacksjs/api": "0.74.47",
|
|
67
|
+
"@stacksjs/auth": "0.74.47",
|
|
68
|
+
"@stacksjs/build": "0.74.47",
|
|
69
69
|
"@stacksjs/bumpx": "^0.2.6",
|
|
70
70
|
"@stacksjs/bunpress": "^0.2.8",
|
|
71
|
-
"@stacksjs/cli": "0.74.
|
|
72
|
-
"@stacksjs/cloud": "0.74.
|
|
73
|
-
"@stacksjs/cms": "0.74.
|
|
74
|
-
"@stacksjs/
|
|
75
|
-
"@stacksjs/
|
|
76
|
-
"@stacksjs/
|
|
77
|
-
"@stacksjs/
|
|
78
|
-
"@stacksjs/
|
|
79
|
-
"@stacksjs/
|
|
80
|
-
"@stacksjs/
|
|
71
|
+
"@stacksjs/cli": "0.74.47",
|
|
72
|
+
"@stacksjs/cloud": "0.74.47",
|
|
73
|
+
"@stacksjs/cms": "0.74.47",
|
|
74
|
+
"@stacksjs/components": "^0.2.246",
|
|
75
|
+
"@stacksjs/config": "0.74.47",
|
|
76
|
+
"@stacksjs/database": "0.74.47",
|
|
77
|
+
"@stacksjs/desktop-build": "0.74.47",
|
|
78
|
+
"@stacksjs/dns": "0.74.47",
|
|
79
|
+
"@stacksjs/enums": "0.74.47",
|
|
80
|
+
"@stacksjs/env": "0.74.47",
|
|
81
|
+
"@stacksjs/error-handling": "0.74.47",
|
|
81
82
|
"@stacksjs/gitit": "^0.2.5",
|
|
82
|
-
"@stacksjs/i18n": "0.74.
|
|
83
|
-
"@stacksjs/image": "0.74.
|
|
84
|
-
"@stacksjs/logging": "0.74.
|
|
83
|
+
"@stacksjs/i18n": "0.74.47",
|
|
84
|
+
"@stacksjs/image": "0.74.47",
|
|
85
|
+
"@stacksjs/logging": "0.74.47",
|
|
85
86
|
"@stacksjs/logsmith": "^0.2.3",
|
|
86
|
-
"@stacksjs/orm": "0.74.
|
|
87
|
-
"@stacksjs/path": "0.74.
|
|
88
|
-
"@stacksjs/payments": "0.74.
|
|
89
|
-
"@stacksjs/queue": "0.74.
|
|
90
|
-
"@stacksjs/registry": "0.74.
|
|
91
|
-
"@stacksjs/router": "0.74.
|
|
92
|
-
"@stacksjs/rpx": "^0.11.
|
|
93
|
-
"@stacksjs/scheduler": "0.74.
|
|
94
|
-
"@stacksjs/search-engine": "0.74.
|
|
95
|
-
"@stacksjs/security": "0.74.
|
|
96
|
-
"@stacksjs/server": "0.74.
|
|
97
|
-
"@stacksjs/sites": "0.74.
|
|
98
|
-
"@stacksjs/storage": "0.74.
|
|
99
|
-
"@stacksjs/strings": "0.74.
|
|
87
|
+
"@stacksjs/orm": "0.74.47",
|
|
88
|
+
"@stacksjs/path": "0.74.47",
|
|
89
|
+
"@stacksjs/payments": "0.74.47",
|
|
90
|
+
"@stacksjs/queue": "0.74.47",
|
|
91
|
+
"@stacksjs/registry": "0.74.47",
|
|
92
|
+
"@stacksjs/router": "0.74.47",
|
|
93
|
+
"@stacksjs/rpx": "^0.11.54",
|
|
94
|
+
"@stacksjs/scheduler": "0.74.47",
|
|
95
|
+
"@stacksjs/search-engine": "0.74.47",
|
|
96
|
+
"@stacksjs/security": "0.74.47",
|
|
97
|
+
"@stacksjs/server": "0.74.47",
|
|
98
|
+
"@stacksjs/sites": "0.74.47",
|
|
99
|
+
"@stacksjs/storage": "0.74.47",
|
|
100
|
+
"@stacksjs/strings": "0.74.47",
|
|
100
101
|
"@stacksjs/stx": "^0.2.286",
|
|
101
102
|
"@stacksjs/tlsx": "^0.13.19",
|
|
102
103
|
"@stacksjs/ts-cloud": "^0.16.0",
|
|
103
104
|
"@stacksjs/ts-md": "^0.1.1",
|
|
104
|
-
"@stacksjs/tunnel": "0.74.
|
|
105
|
-
"@stacksjs/types": "0.74.
|
|
106
|
-
"@stacksjs/utils": "0.74.
|
|
107
|
-
"@stacksjs/whois": "0.74.
|
|
105
|
+
"@stacksjs/tunnel": "0.74.47",
|
|
106
|
+
"@stacksjs/types": "0.74.47",
|
|
107
|
+
"@stacksjs/utils": "0.74.47",
|
|
108
|
+
"@stacksjs/whois": "0.74.47",
|
|
108
109
|
"bun-plugin-stx": "^0.2.286"
|
|
109
110
|
},
|
|
110
111
|
"peerDependencies": {
|
|
@@ -120,7 +121,7 @@
|
|
|
120
121
|
}
|
|
121
122
|
},
|
|
122
123
|
"devDependencies": {
|
|
123
|
-
"@stacksjs/validation": "0.74.
|
|
124
|
+
"@stacksjs/validation": "0.74.47",
|
|
124
125
|
"better-dx": "^0.2.24"
|
|
125
126
|
}
|
|
126
127
|
}
|