@stacksjs/buddy 0.72.82 → 0.72.84
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.d.ts +11 -0
- package/dist/commands/deploy.js +1 -1
- package/package.json +52 -52
|
@@ -298,6 +298,17 @@ export declare function siteDatabaseDrivers(sites: Record<string, any>): string[
|
|
|
298
298
|
* Returns undefined when the migrate step is not a buddy call at all (`bun run
|
|
299
299
|
* migrate`, a shell script, a container exec). Guessing there is how the
|
|
300
300
|
* hard-coded path failed in the first place.
|
|
301
|
+
*
|
|
302
|
+
* Only the invocation, never the shell around it. A migrate step is often
|
|
303
|
+
* wrapped — a retry loop, a `cd &&`, a guard — and taking *everything* before
|
|
304
|
+
* the subcommand used to swallow that wrapper: `ok=0; for i in 1 2 3; do
|
|
305
|
+
* ./buddy migrate` yielded the invocation `ok=0; for i in 1 2 3; do ./buddy`,
|
|
306
|
+
* and the backup command built from it opened a `for … do` that nothing closed.
|
|
307
|
+
* Spliced into the deploy script, that made bash reject the whole thing with
|
|
308
|
+
* `syntax error: unexpected end of file` — pointing at the end of the file,
|
|
309
|
+
* hundreds of lines from the fragment responsible, after the release had
|
|
310
|
+
* already installed and built. So walk back from the subcommand and stop at the
|
|
311
|
+
* first thing that is shell rather than argument.
|
|
301
312
|
*/
|
|
302
313
|
export declare function buddyInvocationFrom(migrateCommand: unknown): string | undefined;
|
|
303
314
|
/**
|
package/dist/commands/deploy.js
CHANGED
|
@@ -15,7 +15,7 @@ ${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.
|
|
17
17
|
${describeSiteClassification(sites)}
|
|
18
|
-
Set \`PORT_API: '${port??"<the api site's port>"}'\` in that site's \`env\` in config/cloud.ts.`}export function siteDatabaseDrivers(sites){const drivers=new Set;for(const site of Object.values(sites??{})){if(typeof site?.start!=="string")continue;if(!(Array.isArray(site?.preStart)?site.preStart:[]).some((step)=>typeof step==="string"&&step.trim().split(/\s+/).some((token)=>migrateToken.test(token))))continue;const env=site?.env??{};drivers.add(String(env.DB_CONNECTION||"sqlite").toLowerCase())}return[...drivers]}const buddyEntrypoint=/(?:^|\/)(?:cli\.[cm]?[jt]s|buddy|bud|stacks)$/,migrateToken=/(?:^|:)migrate(?::|$)/;export function buddyInvocationFrom(migrateCommand){if(typeof migrateCommand!=="string")return;const tokens=migrateCommand.trim().split(/\s+/),at=tokens.findIndex((token)=>migrateToken.test(token));if(at<1)return;const invocation=tokens.slice(
|
|
18
|
+
Set \`PORT_API: '${port??"<the api site's port>"}'\` in that site's \`env\` in config/cloud.ts.`}export function siteDatabaseDrivers(sites){const drivers=new Set;for(const site of Object.values(sites??{})){if(typeof site?.start!=="string")continue;if(!(Array.isArray(site?.preStart)?site.preStart:[]).some((step)=>typeof step==="string"&&step.trim().split(/\s+/).some((token)=>migrateToken.test(token))))continue;const env=site?.env??{};drivers.add(String(env.DB_CONNECTION||"sqlite").toLowerCase())}return[...drivers]}const buddyEntrypoint=/(?:^|\/)(?:cli\.[cm]?[jt]s|buddy|bud|stacks)$/,migrateToken=/(?:^|:)migrate(?::|$)/;export function buddyInvocationFrom(migrateCommand){if(typeof migrateCommand!=="string")return;const tokens=migrateCommand.trim().split(/\s+/),at=tokens.findIndex((token)=>migrateToken.test(token.replace(/[;&|]+$/,"")));if(at<1)return;let from=at;while(from>0&&!isShellToken(tokens[from-1]))from--;const invocation=tokens.slice(from,at),entrypoint=invocation[invocation.length-1];if(!entrypoint||!buddyEntrypoint.test(entrypoint))return;return invocation.join(" ")}function isShellToken(token){if(/[;&|<>()`]/.test(token))return!0;if(/^[A-Za-z_][\w]*=/.test(token))return!0;return SHELL_KEYWORDS.has(token)}const SHELL_KEYWORDS=new Set(["do","done","then","else","elif","fi","if","for","while","until","case","esac","in","function","{","}","!"]);export function preMigrationBackupCommand(backupsDir,migrateCommand){const invocation=buddyInvocationFrom(migrateCommand);if(!invocation)return;return`${invocation} db:backup --before-migrations --out ${backupsDir}`}export function applyPreMigrationBackup(sites,backupsDir){const out={};for(const[name,site]of Object.entries(sites)){const at=migrateIndex(site);if(at===-1){out[name]=site;continue}const preStart=[...site.preStart];if(preStart.some((cmd)=>typeof cmd==="string"&&/\bdb:backup\b/.test(cmd))){out[name]=site;continue}const backup=preMigrationBackupCommand(backupsDir,preStart[at]);if(!backup){log.warn(`No pre-migration backup for "${name}": its migrate step (${String(preStart[at])}) is not a buddy invocation this can reuse. Add a \`db:backup\` command to its preStart to take one.`);out[name]=site;continue}preStart.splice(at,0,backup);out[name]={...site,preStart}}return out}function prefixHostForEnv(host,prefix){if(host.startsWith(`${prefix}.`)||host.startsWith(`www.${prefix}.`))return host;if(host.startsWith("www."))return`www.${prefix}.${host.slice(4)}`;return`${prefix}.${host}`}export function applyEnvironmentToSites(sites,environment,config){const prefix=config?.environments?.[environment]?.domainPrefix;if(!prefix||environment==="production")return sites;const allHosts=[];for(const site of Object.values(sites)){const d=site?.domain;if(typeof d==="string")allHosts.push(d);else if(Array.isArray(d)){for(const x of d)if(typeof x==="string")allHosts.push(x)}}allHosts.sort((a,b)=>b.length-a.length);const rewrite=(val)=>{let r=val;for(const h of allHosts){const esc=h.replace(/[.]/g,"\\.");r=r.replace(new RegExp(`//${esc}(?=[/:?#]|$)`,"g"),`//${prefixHostForEnv(h,prefix)}`)}return r},out={};for(const[name,site]of Object.entries(sites)){if(!site){out[name]=site;continue}const s={...site};if(typeof s.domain==="string")s.domain=prefixHostForEnv(s.domain,prefix);else if(Array.isArray(s.domain))s.domain=s.domain.map((d)=>typeof d==="string"?prefixHostForEnv(d,prefix):d);if(typeof s.redirect==="string")s.redirect=rewrite(s.redirect);if(s.env&&typeof s.env==="object"){const e={...s.env};for(const k of Object.keys(e))if(typeof e[k]==="string")e[k]=rewrite(e[k]);s.env=e}out[name]=s}return out}async function deployToHetzner(tsCloudConfig,deployEnv,options){const verbose=options.verbose===!0,environment=deployEnv==="prod"?"production":deployEnv,apiToken=resolveHetznerApiToken(tsCloudConfig),persistedAttachBox=resolvePersistedAttachTargetBox(tsCloudConfig,environment);if(!apiToken&&!persistedAttachBox){log.error("No Hetzner API token found. Set HCLOUD_TOKEN in your .env (or hetzner.apiToken in config/cloud.ts).");process.exit(ExitCode.FatalError)}const sshPubKey=join(homedir(),".ssh","id_ed25519.pub");if(!existsSync(sshPubKey)){log.error(`SSH public key not found at ${sshPubKey}.`);log.info("ts-cloud deploys to Hetzner over SSH and registers this key on the server.");log.info("Generate one with: ssh-keygen -t ed25519");process.exit(ExitCode.FatalError)}const{createCloudDriver,deployAllComputeSites,ensureManagementDashboard,resolveSiteKind,buildSiteDeployScript}=await loadTsCloudDeployApi(),support=tsCloudPersistentStateSupport(buildSiteDeployScript);if(!support.ok){log.error("This ts-cloud cannot keep your database across deploys, and deploying anyway would destroy it.");log.error(`Missing: ${support.missing.join(", ")}.`);log.error("Upgrade @stacksjs/ts-cloud (bun update @stacksjs/ts-cloud), or point TS_CLOUD_MODULE at a build that has it.");process.exit(ExitCode.FatalError)}const unbacked=findUnbackedManagedServices(tsCloudConfig);if(unbacked.length>0)log.warn(`[deploy] ${unbackedDataMessage(unbacked)}`);try{await runHetznerDeploy({tsCloudConfig,environment,verbose,docker:options.docker===!0,createCloudDriver,deployAllComputeSites,ensureManagementDashboard,resolveSiteKind,onlySite:options.site||void 0,persistedAttachBox})}catch(err){log.error("Hetzner deploy failed:");console.error(err instanceof Error?err.stack||err.message:err);throw err}}export function resolveProjectTsCloudModule(projectRoot=process.cwd()){const manifestPath=join(projectRoot,"node_modules","@stacksjs","ts-cloud","package.json");if(!existsSync(manifestPath))return;const manifest=JSON.parse(readFileSync(manifestPath,"utf8")),rootExport=manifest.exports?.["."],entry=manifest.module??(typeof rootExport==="string"?rootExport:rootExport?.import);if(!entry)return;const modulePath=resolve(dirname(manifestPath),entry);return existsSync(modulePath)?modulePath:void 0}export async function loadTsCloudDeployApi(){const requested=process.env.TS_CLOUD_MODULE?.trim();if(!requested){const projectModule=resolveProjectTsCloudModule();return projectModule?import(pathToFileURL(projectModule).href):import("@stacksjs/ts-cloud")}const modulePath=isAbsolute(requested)?requested:resolve(process.cwd(),requested);if(!existsSync(modulePath))throw Error(`TS_CLOUD_MODULE points to a missing module: ${modulePath}`);return import(pathToFileURL(modulePath).href)}export function shouldInjectManagementDashboard(tsCloudConfig){return!tsCloudConfig.cloud?.attachTo}export function reconcilePartialDeployManagementDashboards(tsCloudConfig,livePorts){const sites=tsCloudConfig.sites,preserved=[],removed=[];if(!sites)return{preserved,removed};for(const[siteName,site]of Object.entries(sites)){if(siteName!=="dashboard"&&!siteName.startsWith("dashboard-"))continue;const livePort=livePorts[siteName];if(typeof livePort!=="number"||!Number.isInteger(livePort)||livePort<1||livePort>65535){delete sites[siteName];removed.push(siteName);continue}const next={...site,port:livePort};if(typeof next.start==="string")next.start=next.start.replace(/(--port(?:=|\s+))\d+/,`$1${livePort}`);sites[siteName]=next;preserved.push(siteName)}return{preserved,removed}}async function reconcilePartialDeployManagementDashboardsWithLiveBox(tsCloudConfig,ip){const slug=String(tsCloudConfig.project?.slug||"app").replace(/[^a-z0-9._-]+/gi,"-"),siteNames=Object.keys(tsCloudConfig.sites??{}).filter((name)=>name==="dashboard"||name.startsWith("dashboard-"));if(siteNames.length===0)return;const units=siteNames.map((siteName)=>({siteName,unit:`${slug}-${siteName}.service`})),probe=`
|
|
19
19
|
const units = ${JSON.stringify(units)}
|
|
20
20
|
const text = bytes => new TextDecoder().decode(bytes).trim()
|
|
21
21
|
const run = args => text(Bun.spawnSync(args).stdout)
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@stacksjs/buddy",
|
|
3
3
|
"type": "module",
|
|
4
4
|
"sideEffects": false,
|
|
5
|
-
"version": "0.72.
|
|
5
|
+
"version": "0.72.84",
|
|
6
6
|
"description": "Meet Buddy. The Stacks runtime.",
|
|
7
7
|
"author": "Chris Breuer",
|
|
8
8
|
"contributors": [
|
|
@@ -95,63 +95,63 @@
|
|
|
95
95
|
"prepublishOnly": "bun run build"
|
|
96
96
|
},
|
|
97
97
|
"dependencies": {
|
|
98
|
-
"@stacksjs/actions": "^0.72.
|
|
99
|
-
"@stacksjs/ai": "^0.72.
|
|
100
|
-
"@stacksjs/alias": "^0.72.
|
|
101
|
-
"@stacksjs/analytics": "^0.72.
|
|
102
|
-
"@stacksjs/api": "^0.72.
|
|
103
|
-
"@stacksjs/arrays": "^0.72.
|
|
104
|
-
"@stacksjs/auth": "^0.72.
|
|
105
|
-
"@stacksjs/browser-extension": "^0.72.
|
|
106
|
-
"@stacksjs/build": "^0.72.
|
|
107
|
-
"@stacksjs/cache": "^0.72.
|
|
108
|
-
"@stacksjs/chat": "^0.72.
|
|
98
|
+
"@stacksjs/actions": "^0.72.84",
|
|
99
|
+
"@stacksjs/ai": "^0.72.84",
|
|
100
|
+
"@stacksjs/alias": "^0.72.84",
|
|
101
|
+
"@stacksjs/analytics": "^0.72.84",
|
|
102
|
+
"@stacksjs/api": "^0.72.84",
|
|
103
|
+
"@stacksjs/arrays": "^0.72.84",
|
|
104
|
+
"@stacksjs/auth": "^0.72.84",
|
|
105
|
+
"@stacksjs/browser-extension": "^0.72.84",
|
|
106
|
+
"@stacksjs/build": "^0.72.84",
|
|
107
|
+
"@stacksjs/cache": "^0.72.84",
|
|
108
|
+
"@stacksjs/chat": "^0.72.84",
|
|
109
109
|
"@stacksjs/clapp": "^0.2.12",
|
|
110
|
-
"@stacksjs/cli": "^0.72.
|
|
111
|
-
"@stacksjs/cloud": "^0.72.
|
|
112
|
-
"@stacksjs/cms": "^0.72.
|
|
113
|
-
"@stacksjs/collections": "^0.72.
|
|
114
|
-
"@stacksjs/config": "^0.72.
|
|
115
|
-
"@stacksjs/database": "^0.72.
|
|
116
|
-
"@stacksjs/desktop-build": "^0.72.
|
|
117
|
-
"@stacksjs/dns": "^0.72.
|
|
110
|
+
"@stacksjs/cli": "^0.72.84",
|
|
111
|
+
"@stacksjs/cloud": "^0.72.84",
|
|
112
|
+
"@stacksjs/cms": "^0.72.84",
|
|
113
|
+
"@stacksjs/collections": "^0.72.84",
|
|
114
|
+
"@stacksjs/config": "^0.72.84",
|
|
115
|
+
"@stacksjs/database": "^0.72.84",
|
|
116
|
+
"@stacksjs/desktop-build": "^0.72.84",
|
|
117
|
+
"@stacksjs/dns": "^0.72.84",
|
|
118
118
|
"@stacksjs/dnsx": "^0.2.3",
|
|
119
|
-
"@stacksjs/email": "^0.72.
|
|
120
|
-
"@stacksjs/enums": "^0.72.
|
|
121
|
-
"@stacksjs/env": "^0.72.
|
|
122
|
-
"@stacksjs/error-handling": "^0.72.
|
|
123
|
-
"@stacksjs/events": "^0.72.
|
|
124
|
-
"@stacksjs/git": "^0.72.
|
|
119
|
+
"@stacksjs/email": "^0.72.84",
|
|
120
|
+
"@stacksjs/enums": "^0.72.84",
|
|
121
|
+
"@stacksjs/env": "^0.72.84",
|
|
122
|
+
"@stacksjs/error-handling": "^0.72.84",
|
|
123
|
+
"@stacksjs/events": "^0.72.84",
|
|
124
|
+
"@stacksjs/git": "^0.72.84",
|
|
125
125
|
"@stacksjs/gitit": "^0.2.5",
|
|
126
|
-
"@stacksjs/health": "^0.72.
|
|
126
|
+
"@stacksjs/health": "^0.72.84",
|
|
127
127
|
"@stacksjs/httx": "^0.1.10",
|
|
128
|
-
"@stacksjs/image": "^0.72.
|
|
129
|
-
"@stacksjs/lint": "^0.72.
|
|
130
|
-
"@stacksjs/logging": "^0.72.
|
|
131
|
-
"@stacksjs/notifications": "^0.72.
|
|
132
|
-
"@stacksjs/objects": "^0.72.
|
|
133
|
-
"@stacksjs/orm": "^0.72.
|
|
134
|
-
"@stacksjs/path": "^0.72.
|
|
135
|
-
"@stacksjs/payments": "^0.72.
|
|
136
|
-
"@stacksjs/realtime": "^0.72.
|
|
137
|
-
"@stacksjs/router": "^0.72.
|
|
128
|
+
"@stacksjs/image": "^0.72.84",
|
|
129
|
+
"@stacksjs/lint": "^0.72.84",
|
|
130
|
+
"@stacksjs/logging": "^0.72.84",
|
|
131
|
+
"@stacksjs/notifications": "^0.72.84",
|
|
132
|
+
"@stacksjs/objects": "^0.72.84",
|
|
133
|
+
"@stacksjs/orm": "^0.72.84",
|
|
134
|
+
"@stacksjs/path": "^0.72.84",
|
|
135
|
+
"@stacksjs/payments": "^0.72.84",
|
|
136
|
+
"@stacksjs/realtime": "^0.72.84",
|
|
137
|
+
"@stacksjs/router": "^0.72.84",
|
|
138
138
|
"@stacksjs/rpx": "^0.11.42",
|
|
139
|
-
"@stacksjs/scheduler": "^0.72.
|
|
140
|
-
"@stacksjs/search-engine": "^0.72.
|
|
141
|
-
"@stacksjs/security": "^0.72.
|
|
142
|
-
"@stacksjs/server": "^0.72.
|
|
143
|
-
"@stacksjs/sites": "^0.72.
|
|
144
|
-
"@stacksjs/skills": "^0.72.
|
|
145
|
-
"@stacksjs/storage": "^0.72.
|
|
146
|
-
"@stacksjs/strings": "^0.72.
|
|
147
|
-
"@stacksjs/testing": "^0.72.
|
|
148
|
-
"@stacksjs/tinker": "^0.72.
|
|
139
|
+
"@stacksjs/scheduler": "^0.72.84",
|
|
140
|
+
"@stacksjs/search-engine": "^0.72.84",
|
|
141
|
+
"@stacksjs/security": "^0.72.84",
|
|
142
|
+
"@stacksjs/server": "^0.72.84",
|
|
143
|
+
"@stacksjs/sites": "^0.72.84",
|
|
144
|
+
"@stacksjs/skills": "^0.72.84",
|
|
145
|
+
"@stacksjs/storage": "^0.72.84",
|
|
146
|
+
"@stacksjs/strings": "^0.72.84",
|
|
147
|
+
"@stacksjs/testing": "^0.72.84",
|
|
148
|
+
"@stacksjs/tinker": "^0.72.84",
|
|
149
149
|
"@stacksjs/ts-cloud": "^0.12.6",
|
|
150
|
-
"@stacksjs/tunnel": "^0.72.
|
|
151
|
-
"@stacksjs/types": "^0.72.
|
|
152
|
-
"@stacksjs/ui": "^0.72.
|
|
153
|
-
"@stacksjs/utils": "^0.72.
|
|
154
|
-
"@stacksjs/validation": "^0.72.
|
|
150
|
+
"@stacksjs/tunnel": "^0.72.84",
|
|
151
|
+
"@stacksjs/types": "^0.72.84",
|
|
152
|
+
"@stacksjs/ui": "^0.72.84",
|
|
153
|
+
"@stacksjs/utils": "^0.72.84",
|
|
154
|
+
"@stacksjs/validation": "^0.72.84",
|
|
155
155
|
"ajv": "^8.20.0",
|
|
156
156
|
"ajv-formats": "^3.0.1",
|
|
157
157
|
"ts-pantry": "^0.11.35"
|