@stacksjs/buddy 0.75.373 → 0.75.377
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/commands/deploy.js +1 -1
- package/package.json +57 -57
package/dist/commands/deploy.js
CHANGED
|
@@ -10,7 +10,7 @@ cloud-init may have failed: SSH in and check /var/log/cloud-init-output.log. Rai
|
|
|
10
10
|
echo "$p \${unit:-unknown}"
|
|
11
11
|
done`,encoding:"utf8",stdio:["pipe","pipe","pipe"]})}catch{return}const clashes=[];for(const line of listing.split(`
|
|
12
12
|
`)){const[portText,unit="unknown"]=line.trim().split(/\s+/),port=Number(portText);if(!Number.isFinite(port)||!wanted.has(port))continue;if(unit.startsWith(`${slug}-`))continue;clashes.push(` ${port} (site '${wanted.get(port)}') is held by ${unit}`)}if(clashes.length===0)return;log.error("Another service on the box is already listening on a port this project wants:");for(const clash of clashes)log.error(clash);log.error("Two services on one port do not error: the kernel load-balances, and each domain serves the other's site about half the time.");log.info("Pick free ports in config/cloud.ts. `ss -lntp` on the box lists what is taken.");process.exit(ExitCode.FatalError)}export async function resolveDeployEnvValues(environment,tsCloudConfig,fileOverride){const fileName=fileOverride??(environment==="production"?".env.production":environment==="staging"?".env.staging":existsSync(p.projectPath(".env.development"))?".env.development":".env"),filePath=p.projectPath(fileName);if(!existsSync(filePath))return{};try{const{getEnv}=await import("@stacksjs/env"),result=getEnv(void 0,{file:fileName,format:"json"});if(!result.success||!result.output)throw Error("The environment could not be read completely");const parsed=JSON.parse(result.output),values={},undecrypted=[];for(const[key,value]of Object.entries(parsed)){if(/^DOTENV_(PUBLIC|PRIVATE)_KEY/.test(key))continue;values[key]=String(value);if(/^(?:encrypted|enc):/.test(values[key]))undecrypted.push(key)}if(undecrypted.length>0)throw Error("The environment still contains ciphertext");const{foreignTenantKeys,partitionTenantEnv}=await import("@stacksjs/env"),partition=partitionTenantEnv(values,{self:tsCloudConfig?.project?.slug,tenants:await resolveDeclaredTenants()});for(const{tenant,keys}of foreignTenantKeys(partition))log.warn(`[deploy] Skipping ${keys.length} '${tenant}' key(s) in ${fileName} - they belong to that tenant's own repository, and shipping them writes its secrets into this project's site .env files. Remove them with: buddy env:check --file ${fileName}. Keys: ${keys.join(", ")}`);return partition.own}catch{throw Error(`Cannot load deployment environment from ${fileName}; restore its decryption key and readable file before deploying.`)}}export function mergeSiteDeployEnv(sites,resolvedDeployEnv){return Object.fromEntries(Object.entries(sites).map(([siteName,site])=>{if(!site)return[siteName,site];const base=site.inheritEnv===!1?{}:{...resolvedDeployEnv};if(site.port!==void 0)delete base.PORT;return[siteName,{...site,env:{...base,...site.env||{}}}]}))}function looksLikeSqliteFile(value){return typeof value==="string"&&/\.(?:sqlite3?|db)$/i.test(value.trim())}export function siteSqlitePath(siteEnv={}){if(String(siteEnv.DB_CONNECTION??"sqlite").trim().toLowerCase()!=="sqlite")return null;const configured=typeof siteEnv.DB_DATABASE_PATH==="string"&&siteEnv.DB_DATABASE_PATH.trim()?siteEnv.DB_DATABASE_PATH:looksLikeSqliteFile(siteEnv.DB_DATABASE)?siteEnv.DB_DATABASE:"database/stacks.sqlite",path=String(configured).trim().replace(/^\.\//,"");if(!path||path.startsWith("/")||path.startsWith("~")||path.split("/").includes(".."))return null;return path}export function tsCloudPersistentStateSupport(buildSiteDeployScript){const missing=[];if(typeof buildSiteDeployScript!=="function")return{ok:!1,missing:["buildSiteDeployScript"]};const target="/var/www/probe-shared/database/probe.sqlite";let script="";try{script=buildSiteDeployScript({siteName:"probe",slug:"probe",appDir:"/var/www/probe-probe",artifactFetch:[],releaseId:"probe",execStart:"/bin/true",envEntries:{},port:3000,sharedPaths:[{path:"database/probe.sqlite",target,seed:!0}]}).join(`
|
|
13
|
-
`)}catch{return{ok:!1,missing:["shared paths with an explicit target"]}}if(!(script.includes("cp -a")&&script.includes("/current/")))missing.push("adoption of existing state into shared/");if(!script.includes(`ln -sfn ${target} `))missing.push("shared paths with an explicit target");return{ok:missing.length===0,missing}}export function projectDatabaseTarget(slug,relativePath){return`/var/www/${slug}-shared/${relativePath}`}function migrateIndex(site){if(!Array.isArray(site?.preStart))return-1;return site.preStart.findIndex((cmd)=>typeof cmd==="string"&&migratesDatabase(cmd))}function migratesDatabase(command){const withoutMessages=command.replace(/'[^']*'/g,"").replace(/"[^"]*"/g,"");if(/\bmigrate\b/.test(withoutMessages))return!0;return!/^\s*(?:echo|printf)\b/.test(command)&&/\bmigrate\b/.test(command)}export function applyAutomaticMigrations(sites){if(Object.values(sites).some((site)=>runsMigrations(site)))return sites;const isServerApp=(site)=>!!site&&typeof site.start==="string",owner=Object.entries(sites).filter(([,site])=>isServerApp(site)).find(([,site])=>site?.migrateOnDeploy!==!1)?.[0];if(!owner)return sites;const site=sites[owner],preStart=Array.isArray(site.preStart)?[...site.preStart]:[];preStart.push("./buddy migrate --no-generate");return{...sites,[owner]:{...site,preStart}}}function runsMigrations(site){return migrateIndex(site)!==-1}export function applyPersistentStatePaths(sites,slug){const isServerApp=(site)=>!!site&&typeof site.start==="string",appSites=Object.entries(sites).filter(([,site])=>isServerApp(site)),
|
|
13
|
+
`)}catch{return{ok:!1,missing:["shared paths with an explicit target"]}}if(!(script.includes("cp -a")&&script.includes("/current/")))missing.push("adoption of existing state into shared/");if(!script.includes(`ln -sfn ${target} `))missing.push("shared paths with an explicit target");return{ok:missing.length===0,missing}}export function projectDatabaseTarget(slug,relativePath){return`/var/www/${slug}-shared/${relativePath}`}function migrateIndex(site){if(!Array.isArray(site?.preStart))return-1;return site.preStart.findIndex((cmd)=>typeof cmd==="string"&&migratesDatabase(cmd))}function migratesDatabase(command){const withoutMessages=command.replace(/'[^']*'/g,"").replace(/"[^"]*"/g,"");if(/\bmigrate\b/.test(withoutMessages))return!0;return!/^\s*(?:echo|printf)\b/.test(command)&&/\bmigrate\b/.test(command)}export function applyAutomaticMigrations(sites){if(Object.values(sites).some((site)=>runsMigrations(site)))return sites;const isServerApp=(site)=>!!site&&typeof site.start==="string",owner=Object.entries(sites).filter(([,site])=>isServerApp(site)).find(([,site])=>site?.migrateOnDeploy!==!1)?.[0];if(!owner)return sites;const site=sites[owner],preStart=Array.isArray(site.preStart)?[...site.preStart]:[];preStart.push("./buddy migrate --no-generate");return{...sites,[owner]:{...site,preStart}}}function runsMigrations(site){return migrateIndex(site)!==-1}export function applyPersistentStatePaths(sites,slug){const isServerApp=(site)=>!!site&&typeof site.start==="string",appSites=Object.entries(sites).filter(([,site])=>isServerApp(site)),owners=new Map;for(const[name,site]of appSites){const sqlite=siteSqlitePath(site.env||{});if(!sqlite)continue;const migrates=runsMigrations(site),previous=owners.get(sqlite);if(!previous||!previous.migrates&&migrates)owners.set(sqlite,{name,migrates})}const out={};for(const[name,site]of Object.entries(sites)){if(!isServerApp(site)){out[name]=site;continue}const sqlite=siteSqlitePath(site.env||{}),framework=["storage/logs"];if(sqlite)framework.unshift({path:sqlite,target:projectDatabaseTarget(slug,sqlite),seed:name===owners.get(sqlite)?.name});const declared=Array.isArray(site.sharedPaths)?site.sharedPaths.filter(Boolean):[],byPath=new Map;for(const entry of[...declared,...framework])byPath.set(typeof entry==="string"?entry:entry.path,entry);out[name]={...site,sharedPaths:[...byPath.values()]}}return out}export function encryptedEnvFileNames(projectRoot){try{return readdirSync(projectRoot).filter((name)=>/^\.env\.[\w-]+$/.test(name)&&name!==".env.example")}catch{return[]}}export function declaresScheduledWork(schedulerFile){if(!existsSync(schedulerFile))return!1;const source=readFileSync(schedulerFile,"utf8").replace(/\/\*[\s\S]*?\*\//g,"").replace(/(^|[^:])\/\/.*$/gm,"$1");return/\bschedule\s*\.\s*(?:job|action|command|call|exec)\b/.test(source)}export function applyScheduledWork(sites,schedulerFile){if(Object.values(sites).some((site)=>site?.scheduler!==void 0))return sites;if(!declaresScheduledWork(schedulerFile))return sites;const appSites=Object.entries(sites).filter(([,site])=>typeof site?.start==="string"),owner=(appSites.find(([,site])=>runsMigrations(site))??appSites[0])?.[0];if(!owner)return sites;return{...sites,[owner]:{...sites[owner],scheduler:!0}}}const apiStartPattern=/\bserve:api\b|(?:^|[\s/"'])(?:serve|storage\/framework\/runtime\/production)\/api\.[cm]?[jt]s(?=$|[\s"';&|<>()])/;function servesApi(name,site){return name==="api"||typeof site?.start==="string"&&apiStartPattern.test(site.start)}function servesHttp(site){return Boolean(site?.port||site?.domain)}function describeSiteClassification(sites){const described=Object.entries(sites).filter(([,site])=>typeof site?.start==="string").map(([name,site])=>{if(servesApi(name,site))return`\`${name}\` (api)`;if(!servesHttp(site))return`\`${name}\` (headless, no HTTP surface)`;const env=site?.env??{},wiring=env.API_URL?"API_URL set":env.PORT_API?"PORT_API set":"no API_URL or PORT_API";return`\`${name}\` (page, ${wiring})`});return described.length>0?`Sites examined: ${described.join(", ")}.`:"No server-app sites were examined."}function isDashboardSite(name){return name==="dashboard"||name.startsWith("dashboard-")}export function apiDeploymentProblem(sites,hasApiRoutes){if(!hasApiRoutes)return;const entries=Object.entries(sites),appSites=entries.filter(([,site])=>typeof site?.start==="string");if(appSites.length===0)return;const api=entries.find(([name,site])=>servesApi(name,site)),pages=appSites.filter(([name,site])=>!servesApi(name,site)&&!isDashboardSite(name)&&servesHttp(site)),configured=(site)=>{const env=site?.env??{};return Boolean(env.API_URL||env.PORT_API)};if(!api){if(pages.every(([,site])=>configured(site)))return;return`This project declares API routes and no site serves them. \`/api/**\` will answer 502 on every request.
|
|
14
14
|
${describeSiteClassification(sites)}
|
|
15
15
|
Add an \`api\` site to config/cloud.ts running \`buddy serve:api\` on its own port, and set \`PORT_API\` on the site that serves the pages so its proxy can find it.
|
|
16
16
|
Set \`API_URL\` on the page site instead when the API lives on another host.`}const unwired=pages.filter(([,site])=>!configured(site));if(unwired.length===0)return;const port=api[1]?.port;return`The \`${api[0]}\` site serves the API, but ${unwired.map(([name])=>`\`${name}\``).join(", ")} will not proxy to it: neither \`PORT_API\` nor \`API_URL\` is set in its environment, so \`/api/**\` answers 502.
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@stacksjs/buddy",
|
|
3
3
|
"type": "module",
|
|
4
4
|
"sideEffects": false,
|
|
5
|
-
"version": "0.75.
|
|
5
|
+
"version": "0.75.377",
|
|
6
6
|
"description": "Meet Buddy. The Stacks runtime.",
|
|
7
7
|
"author": "Chris Breuer",
|
|
8
8
|
"contributors": [
|
|
@@ -95,73 +95,73 @@
|
|
|
95
95
|
"prepublishOnly": "bun run build"
|
|
96
96
|
},
|
|
97
97
|
"dependencies": {
|
|
98
|
-
"@stacksjs/actions": "0.75.
|
|
99
|
-
"@stacksjs/ai": "0.75.
|
|
100
|
-
"@stacksjs/alias": "0.75.
|
|
101
|
-
"@stacksjs/analytics": "0.75.
|
|
102
|
-
"@stacksjs/api": "0.75.
|
|
103
|
-
"@stacksjs/arrays": "0.75.
|
|
104
|
-
"@stacksjs/auth": "0.75.
|
|
105
|
-
"@stacksjs/browser-extension": "0.75.
|
|
106
|
-
"@stacksjs/build": "0.75.
|
|
98
|
+
"@stacksjs/actions": "0.75.377",
|
|
99
|
+
"@stacksjs/ai": "0.75.377",
|
|
100
|
+
"@stacksjs/alias": "0.75.377",
|
|
101
|
+
"@stacksjs/analytics": "0.75.377",
|
|
102
|
+
"@stacksjs/api": "0.75.377",
|
|
103
|
+
"@stacksjs/arrays": "0.75.377",
|
|
104
|
+
"@stacksjs/auth": "0.75.377",
|
|
105
|
+
"@stacksjs/browser-extension": "0.75.377",
|
|
106
|
+
"@stacksjs/build": "0.75.377",
|
|
107
107
|
"@stacksjs/bumpx": "^0.2.26",
|
|
108
|
-
"@stacksjs/cache": "0.75.
|
|
109
|
-
"@stacksjs/chat": "0.75.
|
|
108
|
+
"@stacksjs/cache": "0.75.377",
|
|
109
|
+
"@stacksjs/chat": "0.75.377",
|
|
110
110
|
"@stacksjs/clapp": "^0.2.16",
|
|
111
|
-
"@stacksjs/cli": "0.75.
|
|
112
|
-
"@stacksjs/cloud": "0.75.
|
|
113
|
-
"@stacksjs/cms": "0.75.
|
|
114
|
-
"@stacksjs/collections": "0.75.
|
|
115
|
-
"@stacksjs/commerce": "0.75.
|
|
116
|
-
"@stacksjs/config": "0.75.
|
|
117
|
-
"@stacksjs/database": "0.75.
|
|
118
|
-
"@stacksjs/desktop-build": "0.75.
|
|
119
|
-
"@stacksjs/dns": "0.75.
|
|
111
|
+
"@stacksjs/cli": "0.75.377",
|
|
112
|
+
"@stacksjs/cloud": "0.75.377",
|
|
113
|
+
"@stacksjs/cms": "0.75.377",
|
|
114
|
+
"@stacksjs/collections": "0.75.377",
|
|
115
|
+
"@stacksjs/commerce": "0.75.377",
|
|
116
|
+
"@stacksjs/config": "0.75.377",
|
|
117
|
+
"@stacksjs/database": "0.75.377",
|
|
118
|
+
"@stacksjs/desktop-build": "0.75.377",
|
|
119
|
+
"@stacksjs/dns": "0.75.377",
|
|
120
120
|
"@stacksjs/dnsx": "^0.2.3",
|
|
121
|
-
"@stacksjs/email": "0.75.
|
|
122
|
-
"@stacksjs/enums": "0.75.
|
|
123
|
-
"@stacksjs/env": "0.75.
|
|
124
|
-
"@stacksjs/error-handling": "0.75.
|
|
125
|
-
"@stacksjs/events": "0.75.
|
|
126
|
-
"@stacksjs/features": "0.75.
|
|
127
|
-
"@stacksjs/git": "0.75.
|
|
121
|
+
"@stacksjs/email": "0.75.377",
|
|
122
|
+
"@stacksjs/enums": "0.75.377",
|
|
123
|
+
"@stacksjs/env": "0.75.377",
|
|
124
|
+
"@stacksjs/error-handling": "0.75.377",
|
|
125
|
+
"@stacksjs/events": "0.75.377",
|
|
126
|
+
"@stacksjs/features": "0.75.377",
|
|
127
|
+
"@stacksjs/git": "0.75.377",
|
|
128
128
|
"@stacksjs/gitit": "^0.2.5",
|
|
129
|
-
"@stacksjs/health": "0.75.
|
|
129
|
+
"@stacksjs/health": "0.75.377",
|
|
130
130
|
"@stacksjs/httx": "^0.1.10",
|
|
131
|
-
"@stacksjs/image": "0.75.
|
|
132
|
-
"@stacksjs/lint": "0.75.
|
|
133
|
-
"@stacksjs/logging": "0.75.
|
|
134
|
-
"@stacksjs/notifications": "0.75.
|
|
135
|
-
"@stacksjs/objects": "0.75.
|
|
136
|
-
"@stacksjs/orm": "0.75.
|
|
137
|
-
"@stacksjs/path": "0.75.
|
|
138
|
-
"@stacksjs/payments": "0.75.
|
|
139
|
-
"@stacksjs/realtime": "0.75.
|
|
140
|
-
"@stacksjs/router": "0.75.
|
|
131
|
+
"@stacksjs/image": "0.75.377",
|
|
132
|
+
"@stacksjs/lint": "0.75.377",
|
|
133
|
+
"@stacksjs/logging": "0.75.377",
|
|
134
|
+
"@stacksjs/notifications": "0.75.377",
|
|
135
|
+
"@stacksjs/objects": "0.75.377",
|
|
136
|
+
"@stacksjs/orm": "0.75.377",
|
|
137
|
+
"@stacksjs/path": "0.75.377",
|
|
138
|
+
"@stacksjs/payments": "0.75.377",
|
|
139
|
+
"@stacksjs/realtime": "0.75.377",
|
|
140
|
+
"@stacksjs/router": "0.75.377",
|
|
141
141
|
"@stacksjs/rpx": "^0.11.54",
|
|
142
|
-
"@stacksjs/scheduler": "0.75.
|
|
143
|
-
"@stacksjs/search-engine": "0.75.
|
|
144
|
-
"@stacksjs/security": "0.75.
|
|
145
|
-
"@stacksjs/server": "0.75.
|
|
146
|
-
"@stacksjs/sites": "0.75.
|
|
147
|
-
"@stacksjs/skills": "0.75.
|
|
148
|
-
"@stacksjs/socials": "0.75.
|
|
149
|
-
"@stacksjs/storage": "0.75.
|
|
150
|
-
"@stacksjs/strings": "0.75.
|
|
142
|
+
"@stacksjs/scheduler": "0.75.377",
|
|
143
|
+
"@stacksjs/search-engine": "0.75.377",
|
|
144
|
+
"@stacksjs/security": "0.75.377",
|
|
145
|
+
"@stacksjs/server": "0.75.377",
|
|
146
|
+
"@stacksjs/sites": "0.75.377",
|
|
147
|
+
"@stacksjs/skills": "0.75.377",
|
|
148
|
+
"@stacksjs/socials": "0.75.377",
|
|
149
|
+
"@stacksjs/storage": "0.75.377",
|
|
150
|
+
"@stacksjs/strings": "0.75.377",
|
|
151
151
|
"@stacksjs/stx": "0.2.744",
|
|
152
|
-
"@stacksjs/testing": "0.75.
|
|
153
|
-
"@stacksjs/tinker": "0.75.
|
|
152
|
+
"@stacksjs/testing": "0.75.377",
|
|
153
|
+
"@stacksjs/tinker": "0.75.377",
|
|
154
154
|
"@stacksjs/tlsx": "^0.13.19",
|
|
155
|
-
"@stacksjs/ts-cloud": "^0.16.
|
|
156
|
-
"@stacksjs/tunnel": "0.75.
|
|
157
|
-
"@stacksjs/types": "0.75.
|
|
158
|
-
"@stacksjs/ui": "0.75.
|
|
159
|
-
"@stacksjs/utils": "0.75.
|
|
160
|
-
"@stacksjs/validation": "0.75.
|
|
155
|
+
"@stacksjs/ts-cloud": "^0.16.89",
|
|
156
|
+
"@stacksjs/tunnel": "0.75.377",
|
|
157
|
+
"@stacksjs/types": "0.75.377",
|
|
158
|
+
"@stacksjs/ui": "0.75.377",
|
|
159
|
+
"@stacksjs/utils": "0.75.377",
|
|
160
|
+
"@stacksjs/validation": "0.75.377",
|
|
161
161
|
"ajv": "^8.20.0",
|
|
162
162
|
"ajv-formats": "^3.0.1",
|
|
163
163
|
"bun-plugin-stx": "0.2.744",
|
|
164
|
-
"ts-pantry": "^0.11.
|
|
164
|
+
"ts-pantry": "^0.11.146"
|
|
165
165
|
},
|
|
166
166
|
"devDependencies": {
|
|
167
167
|
"better-dx": "^0.2.24"
|