@stacksjs/actions 0.72.37 → 0.72.39
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/build/core.js +1 -1
- package/dist/dev/api.js +1 -1
- package/dist/dev/migrate-watcher.js +1 -1
- package/dist/dev/views.js +1 -1
- package/dist/generate/images.js +1 -1
- package/dist/generate/index.js +1 -1
- package/dist/make.js +1 -1
- package/dist/queue/work.js +1 -1
- package/dist/serve/dashboard.js +1 -1
- package/dist/upgrade/framework.js +2 -2
- package/dist/upgrade/packages.js +3 -3
- package/package.json +20 -20
package/dist/build/core.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{$}from"bun";import{existsSync,readFileSync,statSync}from"node:fs";import{basename}from"node:path";import process from"node:process";import{dim,italic,log}from"@stacksjs/cli";import{feature}from"@stacksjs/config";import{corePath}from"@stacksjs/path";import{glob}from"@stacksjs/storage";import{ExitCode}from"@stacksjs/types";log.info("Building core packages");const FEATURE_GATED_PACKAGES={cms:"cms",commerce:"commerce",queue:"queue",realtime:"realtime"},allEntries=(await glob([corePath("*")],{onlyFiles:!1})).sort(),dirs=allEntries.filter((entry)=>{if(!existsSync(entry)||!statSync(entry).isDirectory())return!1;const pkgPath=`${entry}/package.json`;if(!existsSync(pkgPath))return!1;const name=basename(entry),gate=FEATURE_GATED_PACKAGES[name];if(gate&&!feature(gate)){log.info(`Skipping ${italic(dim(entry))}
|
|
1
|
+
import{$}from"bun";import{existsSync,readFileSync,statSync}from"node:fs";import{basename}from"node:path";import process from"node:process";import{dim,italic,log}from"@stacksjs/cli";import{feature}from"@stacksjs/config";import{corePath}from"@stacksjs/path";import{glob}from"@stacksjs/storage";import{ExitCode}from"@stacksjs/types";log.info("Building core packages");const FEATURE_GATED_PACKAGES={cms:"cms",commerce:"commerce",queue:"queue",realtime:"realtime"},allEntries=(await glob([corePath("*")],{onlyFiles:!1})).sort(),dirs=allEntries.filter((entry)=>{if(!existsSync(entry)||!statSync(entry).isDirectory())return!1;const pkgPath=`${entry}/package.json`;if(!existsSync(pkgPath))return!1;const name=basename(entry),gate=FEATURE_GATED_PACKAGES[name];if(gate&&!feature(gate)){log.info(`Skipping ${italic(dim(entry))} - feature '${gate}' is disabled`);return!1}try{return typeof JSON.parse(readFileSync(pkgPath,"utf8")).scripts?.build==="string"}catch(error){log.warn(`Skipping ${italic(dim(entry))}: package.json could not be read`);return!1}});if(dirs.length===0){log.info("No core packages found");process.exit(ExitCode.FatalError)}const failed=[];for(const folder of dirs){log.info(`\uD83C\uDFD7\uFE0F Building ${italic(dim(folder))}`);try{$.cwd(folder);await $`bun run build`;log.success(`${italic(dim(folder))} built`)}catch(error){log.warn(`Failed to build ${italic(dim(folder))}, skipping...`);failed.push(folder)}console.log("")}if(failed.length>0){log.warn(`${failed.length} package(s) failed to build:`);for(const f of failed)log.warn(` - ${f}`)}
|
package/dist/dev/api.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{existsSync}from"node:fs";import process from"node:process";import{parseOptions}from"@stacksjs/cli";import{config,overridesReady}from"@stacksjs/config";import{path}from"@stacksjs/path";import{disableViewRouting,route}from"@stacksjs/router";import{autoImportsAreStale,generateAutoImportFiles,generateServerAutoImportTypes,injectGlobalAutoImports}from"@stacksjs/server";import{resolveApiHost}from"../helpers/api-host";import{exitWithParent}from"./exit-with-parent";const _options=parseOptions();exitWithParent();await generateServerAutoImportTypes();await overridesReady;const port=Number(process.env.PORT_API)||config.ports?.api||3008,hostname=resolveApiHost(),modelsIndex=path.storagePath("framework/auto-imports/models.ts");if(!existsSync(modelsIndex)||autoImportsAreStale())await generateAutoImportFiles();await injectGlobalAutoImports();try{const typesenseDev=path.appPath("Lib/typesense-dev.ts");if(existsSync(typesenseDev)){const mod=await import(typesenseDev);if(typeof mod.ensureTypesenseRunning==="function")await mod.ensureTypesenseRunning()}}catch(err){const{log}=await import("@stacksjs/cli");log.warn(`[api:dev] search engine bootstrap skipped: ${err.message}`)}try{const listenerPath=path.appPath("Listener.ts");if(existsSync(listenerPath)){const listener=await import(listenerPath);if(typeof listener.handleEvents==="function")await listener.handleEvents()}}catch(err){const{log}=await import("@stacksjs/cli");log.warn(`[api:dev] failed to bootstrap event listeners
|
|
1
|
+
import{existsSync}from"node:fs";import process from"node:process";import{parseOptions}from"@stacksjs/cli";import{config,overridesReady}from"@stacksjs/config";import{path}from"@stacksjs/path";import{disableViewRouting,route}from"@stacksjs/router";import{autoImportsAreStale,generateAutoImportFiles,generateServerAutoImportTypes,injectGlobalAutoImports}from"@stacksjs/server";import{resolveApiHost}from"../helpers/api-host";import{exitWithParent}from"./exit-with-parent";const _options=parseOptions();exitWithParent();await generateServerAutoImportTypes();await overridesReady;const port=Number(process.env.PORT_API)||config.ports?.api||3008,hostname=resolveApiHost(),modelsIndex=path.storagePath("framework/auto-imports/models.ts");if(!existsSync(modelsIndex)||autoImportsAreStale())await generateAutoImportFiles();await injectGlobalAutoImports();try{const typesenseDev=path.appPath("Lib/typesense-dev.ts");if(existsSync(typesenseDev)){const mod=await import(typesenseDev);if(typeof mod.ensureTypesenseRunning==="function")await mod.ensureTypesenseRunning()}}catch(err){const{log}=await import("@stacksjs/cli");log.warn(`[api:dev] search engine bootstrap skipped: ${err.message}`)}try{const listenerPath=path.appPath("Listener.ts");if(existsSync(listenerPath)){const listener=await import(listenerPath);if(typeof listener.handleEvents==="function")await listener.handleEvents()}}catch(err){const{log}=await import("@stacksjs/cli");log.warn(`[api:dev] failed to bootstrap event listeners - dispatched events will be ignored: ${err.message}`)}try{const{startModelMigrationWatcher}=await import("./migrate-watcher");startModelMigrationWatcher()}catch(err){const{log}=await import("@stacksjs/cli");log.debug(`[api:dev] auto-migrate watcher not started: ${err.message}`)}function resolveDefaultsCorsPath(){const vendored=path.frameworkPath("defaults/app/Middleware/Cors.ts");if(existsSync(vendored))return vendored;try{return`${Bun.resolveSync("@stacksjs/defaults/package.json",process.cwd()).replace(/package\.json$/,"")}app/Middleware/Cors.ts`}catch{return vendored}}const corsMod=await import(resolveDefaultsCorsPath()),corsMiddleware=corsMod.default;route.use(corsMiddleware.toRouterHandler());const SAFE_REQUEST_ID=/^[a-zA-Z0-9._-]{8,128}$/;route.use(async(request,next)=>{const inbound=request?.headers?.get?.("x-request-id")||request?.headers?.get?.("X-Request-ID")||"",id=typeof inbound==="string"&&SAFE_REQUEST_ID.test(inbound)?inbound:crypto.randomUUID();request._requestId=id;request._startNs=process.hrtime.bigint();const response=await next();if(response&&response.status===404&&(response.headers.get("content-type")||"").includes("json"))try{const body=await response.clone().json();if(body&&(body.message==="Not Found"||body.error==="Not Found")){const url=new URL(request.url),enriched={...body,path:url.pathname,method:request.method,request_id:id},headers=new Headers(response.headers);headers.set("X-Request-ID",id);return new Response(JSON.stringify(enriched),{status:404,headers})}}catch{}return response});await route.importRoutes();disableViewRouting(route.bunRouter);try{await route.serve({port,hostname});console.log(`
|
|
2
2
|
\u279C API server ready: http://${hostname}:${port}
|
|
3
3
|
`)}catch(err){if((err?.code||err?.errno)==="EADDRINUSE"||String(err?.message||"").includes("EADDRINUSE")){console.error(`
|
|
4
4
|
[dev/api] Port ${port} is already in use. Kill the other process and re-run \`./buddy dev\`, or set PORT_API to another port.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{existsSync,watch}from"node:fs";import process from"node:process";import{log}from"@stacksjs/logging";import{path}from"@stacksjs/path";let debounce,running=!1,queued=!1;export function startModelMigrationWatcher(){if(["0","false","off"].includes(String(process.env.STACKS_DEV_AUTO_MIGRATE).toLowerCase()))return;const dirs=[path.userModelsPath(""),path.storagePath("framework/models")].filter((d)=>{try{return existsSync(d)}catch{return!1}});if(dirs.length===0)return;const schedule=(filename)=>{if(filename&&!filename.endsWith(".ts"))return;if(debounce)clearTimeout(debounce);debounce=setTimeout(()=>void reconcile(),400)};let watching=0;for(const dir of dirs)try{watch(dir,{recursive:!0},(_event,filename)=>schedule(filename));watching+=1}catch(err){log.debug(`[dev] model watch failed for ${dir}: ${err instanceof Error?err.message:String(err)}`)}if(watching>0)log.debug("[dev] auto-migrate watcher active
|
|
1
|
+
import{existsSync,watch}from"node:fs";import process from"node:process";import{log}from"@stacksjs/logging";import{path}from"@stacksjs/path";let debounce,running=!1,queued=!1;export function startModelMigrationWatcher(){if(["0","false","off"].includes(String(process.env.STACKS_DEV_AUTO_MIGRATE).toLowerCase()))return;const dirs=[path.userModelsPath(""),path.storagePath("framework/models")].filter((d)=>{try{return existsSync(d)}catch{return!1}});if(dirs.length===0)return;const schedule=(filename)=>{if(filename&&!filename.endsWith(".ts"))return;if(debounce)clearTimeout(debounce);debounce=setTimeout(()=>void reconcile(),400)};let watching=0;for(const dir of dirs)try{watch(dir,{recursive:!0},(_event,filename)=>schedule(filename));watching+=1}catch(err){log.debug(`[dev] model watch failed for ${dir}: ${err instanceof Error?err.message:String(err)}`)}if(watching>0)log.debug("[dev] auto-migrate watcher active - model edits will sync the database")}async function reconcile(){if(running){queued=!0;return}running=!0;try{const{previewPendingMigrations,generateMigrations,runDatabaseMigration}=await import("@stacksjs/database"),ops=await previewPendingMigrations();if(ops.length===0)return;const destructive=ops.filter((o)=>o.destructive);if(destructive.length>0){log.warn(`[dev] ${destructive.length} destructive schema change${destructive.length===1?"":"s"} detected - not auto-applied. Run \`buddy migrate\` to review and apply.`);return}log.info(`[dev] Syncing database to models (${ops.length} change${ops.length===1?"":"s"})...`);const gen=await generateMigrations();if(gen.isErr){log.error("[dev] auto-migrate: generation failed",gen.error);return}const run=await runDatabaseMigration();if(run.isErr){log.error("[dev] auto-migrate: apply failed",run.error);return}log.success("[dev] Database is in sync with your models.")}catch(err){log.warn(`[dev] auto-migrate skipped: ${err instanceof Error?err.message:String(err)}`)}finally{running=!1;if(queued){queued=!1;if(debounce)clearTimeout(debounce);debounce=setTimeout(()=>void reconcile(),400)}}}
|
package/dist/dev/views.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{AsyncLocalStorage}from"node:async_hooks";import{existsSync}from"node:fs";import{join}from"node:path";import{config,installRequestContext,overridesReady,parseCookieHeader,resolveViewPatterns}from"@stacksjs/config";import{log}from"@stacksjs/logging";import{projectPath}from"@stacksjs/path";import{seedCsrfPageResponse}from"./csrf";import{resolveDefaultsResources}from"./defaults-resources";import{exitWithParent}from"./exit-with-parent";const requestStore=new AsyncLocalStorage;function currentRequestContext(){return requestStore.getStore()??globalThis.__stxServeContext}installRequestContext(currentRequestContext);exitWithParent();const projectServe=projectPath("serve.ts");try{if(await Bun.file(projectServe).exists())await import(projectServe);else await startDefaultServer()}catch{await startDefaultServer()}function parseCookies(req){return parseCookieHeader(req.headers.get("cookie"))}async function startDefaultServer(){await overridesReady;const{applyViewSecurityHeaders,describeApiProxyRules,describeRedirectRules,injectGlobalAutoImports,isApiBoundRequest,proxyToBackend,resolveApiProxyRules,resolveEmbeddableRules,resolveRedirect,resolveRedirectRules}=await import("@stacksjs/server"),{applyRequestLocale}=await import("@stacksjs/i18n");await injectGlobalAutoImports();const{serve}=await import("bun-plugin-stx/serve"),{site:siteConfig,i18n:i18nConfig}=await loadStxSiteConfig(),defaultsResources=resolveDefaultsResources(),userViewsPath="resources/views",defaultViewsPath=join(defaultsResources,"views"),userLayoutsPath=await firstExistingPath(["resources/views/layouts","resources/layouts"])??"resources/views/layouts",defaultLayoutsPath=join(defaultsResources,"layouts"),userPartialsPath=await firstExistingPath(["resources/partials","resources/views/partials","partials","resources/components"]),preferredPort=Number(process.env.PORT)||3000,apiPort=Number(process.env.PORT_API)||3008,docsPort=Number(process.env.PORT_DOCS)||config.ports?.docs||3006,apiBase=`http://127.0.0.1:${apiPort}`,docsBase=`http://127.0.0.1:${docsPort}`,apiProxyRules=resolveApiProxyRules(config.server?.proxy),redirectRules=resolveRedirectRules(config.server?.redirects),embeddableRules=resolveEmbeddableRules(config.server?.security?.embeddable);if(apiProxyRules.paths.length>0||apiProxyRules.prefixes.length>1)console.log(` API proxy: ${describeApiProxyRules(apiProxyRules)}`);if(embeddableRules.paths.length>0||embeddableRules.prefixes.length>0)console.log(` Frameable by other origins: ${[...embeddableRules.paths,...embeddableRules.prefixes].join(" ")}`);if(redirectRules.size>0)console.log(` Redirects: ${describeRedirectRules(redirectRules)}`);const hasUserDocsView=["docs.stx","docs/index.stx"].some((rel)=>existsSync(projectPath(`${userViewsPath}/${rel}`))),hasDocsSite=existsSync(projectPath("docs")),docsProxyEnabled=!hasUserDocsView&&hasDocsSite,{authCookieName,stxPageAuthMiddleware}=await import("@stacksjs/auth"),authCookie=authCookieName(),viewPatterns=resolveViewPatterns(userViewsPath,defaultViewsPath,config?.ui?.defaultViews,(path)=>existsSync(projectPath(path)));for(const name of viewPatterns.missing)log.warn(`ui.defaultViews lists "${name}", which does not exist under ${defaultViewsPath}
|
|
1
|
+
import{AsyncLocalStorage}from"node:async_hooks";import{existsSync}from"node:fs";import{join}from"node:path";import{config,installRequestContext,overridesReady,parseCookieHeader,resolveViewPatterns}from"@stacksjs/config";import{log}from"@stacksjs/logging";import{projectPath}from"@stacksjs/path";import{seedCsrfPageResponse}from"./csrf";import{resolveDefaultsResources}from"./defaults-resources";import{exitWithParent}from"./exit-with-parent";const requestStore=new AsyncLocalStorage;function currentRequestContext(){return requestStore.getStore()??globalThis.__stxServeContext}installRequestContext(currentRequestContext);exitWithParent();const projectServe=projectPath("serve.ts");try{if(await Bun.file(projectServe).exists())await import(projectServe);else await startDefaultServer()}catch{await startDefaultServer()}function parseCookies(req){return parseCookieHeader(req.headers.get("cookie"))}async function startDefaultServer(){await overridesReady;const{applyViewSecurityHeaders,describeApiProxyRules,describeRedirectRules,injectGlobalAutoImports,isApiBoundRequest,proxyToBackend,resolveApiProxyRules,resolveEmbeddableRules,resolveRedirect,resolveRedirectRules}=await import("@stacksjs/server"),{applyRequestLocale}=await import("@stacksjs/i18n");await injectGlobalAutoImports();const{serve}=await import("bun-plugin-stx/serve"),{site:siteConfig,i18n:i18nConfig}=await loadStxSiteConfig(),defaultsResources=resolveDefaultsResources(),userViewsPath="resources/views",defaultViewsPath=join(defaultsResources,"views"),userLayoutsPath=await firstExistingPath(["resources/views/layouts","resources/layouts"])??"resources/views/layouts",defaultLayoutsPath=join(defaultsResources,"layouts"),userPartialsPath=await firstExistingPath(["resources/partials","resources/views/partials","partials","resources/components"]),preferredPort=Number(process.env.PORT)||3000,apiPort=Number(process.env.PORT_API)||3008,docsPort=Number(process.env.PORT_DOCS)||config.ports?.docs||3006,apiBase=`http://127.0.0.1:${apiPort}`,docsBase=`http://127.0.0.1:${docsPort}`,apiProxyRules=resolveApiProxyRules(config.server?.proxy),redirectRules=resolveRedirectRules(config.server?.redirects),embeddableRules=resolveEmbeddableRules(config.server?.security?.embeddable);if(apiProxyRules.paths.length>0||apiProxyRules.prefixes.length>1)console.log(` API proxy: ${describeApiProxyRules(apiProxyRules)}`);if(embeddableRules.paths.length>0||embeddableRules.prefixes.length>0)console.log(` Frameable by other origins: ${[...embeddableRules.paths,...embeddableRules.prefixes].join(" ")}`);if(redirectRules.size>0)console.log(` Redirects: ${describeRedirectRules(redirectRules)}`);const hasUserDocsView=["docs.stx","docs/index.stx"].some((rel)=>existsSync(projectPath(`${userViewsPath}/${rel}`))),hasDocsSite=existsSync(projectPath("docs")),docsProxyEnabled=!hasUserDocsView&&hasDocsSite,{authCookieName,stxPageAuthMiddleware}=await import("@stacksjs/auth"),authCookie=authCookieName(),viewPatterns=resolveViewPatterns(userViewsPath,defaultViewsPath,config?.ui?.defaultViews,(path)=>existsSync(projectPath(path)));for(const name of viewPatterns.missing)log.warn(`ui.defaultViews lists "${name}", which does not exist under ${defaultViewsPath} - ignoring.`);await serve({patterns:viewPatterns.patterns,port:preferredPort,componentsDir:join(defaultsResources,"components"),layoutsDir:userLayoutsPath,...userPartialsPath&&{partialsDir:userPartialsPath},fallbackLayoutsDir:defaultLayoutsPath,fallbackPartialsDir:defaultViewsPath,quiet:!0,openPath:process.env.STACKS_DEV_ENTRY_PATH||"/",...i18nConfig&&{i18n:i18nConfig},...siteConfig?.url&&{site:siteConfig},auth:{cookieName:authCookie,redirectTo:"/login"},middleware:stxPageAuthMiddleware({cookieName:authCookie,redirectTo:"/login"}),onRequest:async(req)=>{const url=new URL(req.url),{maintenanceGate}=await import("@stacksjs/server"),gated=await maintenanceGate(req);if(gated)return gated;const redirected=resolveRedirect(url,redirectRules);if(redirected)return redirected;if(existsSync(projectPath("resources/views/blog.stx"))){const{renderBlogFeed}=await import("../blog"),feed=await renderBlogFeed(req);if(feed)return feed}else{const{renderBlog}=await import("../blog"),blogResponse=await renderBlog(req);if(blogResponse)return blogResponse}if(docsProxyEnabled&&(url.pathname==="/docs"||url.pathname.startsWith("/docs/")))return proxyToBackend(req,docsBase,"/docs");if(isApiBoundRequest(req,url.pathname,apiProxyRules))return proxyToBackend(req,apiBase);if(i18nConfig&&req.method==="GET"){const localeSwitch=url.pathname.match(/^\/locale\/([a-z]{2}(?:-[a-z]{2})?)\/?$/i);if(localeSwitch){const{createLocaleSwitchResponse}=await import("@stacksjs/i18n");return createLocaleSwitchResponse(req,localeSwitch[1],i18nConfig)}}const locale=await applyRequestLocale(req);let site=null;if(config.sites?.enabled){const sites=await import("@stacksjs/sites"),resolved=await sites.resolveSiteByHost(sites.requestHost(req.headers,sites.sitesOptions()));sites.setCurrentSite(resolved);site=sites.toSiteSnapshot(resolved)}const ctx={cookies:parseCookies(req),url:req.url,path:url.pathname,search:url.search,host:url.host,locale,site};globalThis.__stxServeSearch=url.search;globalThis.__stxServeContext=ctx;if(config.analytics?.capturePageviews)import("@stacksjs/analytics").then(({recordPageview})=>recordPageview(req)).catch(()=>{});requestStore.enterWith(ctx);return null},onResponse:async(req,response)=>{const secured=applyViewSecurityHeaders(req,response,embeddableRules),baseline=secured??response;let current=baseline;if(current.status===404&&config.sites?.enabled)try{const{cmsNotFoundFallback}=await import("@stacksjs/cms"),cmsResponse=await cmsNotFoundFallback(req);if(cmsResponse)current=cmsResponse}catch(error){log.debug(`CMS fallback skipped: ${error.message}`)}return await seedCsrfPageResponse(req,current)??(current===baseline?secured:current)}})}async function firstExistingPath(candidates){const existing=candidates.filter((candidate)=>existsSync(projectPath(candidate)));if(existing.length===0)return null;for(const candidate of existing)if(containsTemplates(projectPath(candidate)))return candidate;return existing[0]}function containsTemplates(dir){try{return[...new Bun.Glob("**/*.stx").scanSync({cwd:dir,onlyFiles:!0})].length>0}catch{return!1}}function fallbackI18nFromSite(site){const locales=site.i18n.locales,defaultLocale=site.i18n.defaultLocale??locales[0];return{locales,defaultLocale,labels:site.i18n.labels??Object.fromEntries(locales.map((c)=>[c,c.toUpperCase()])),translations:{},pickerSelector:site.i18n.pickerSelector??"#lang-picker"}}async function resolveSiteI18n(site){try{const{resolveI18n}=await import("@stacksjs/stx");if(typeof resolveI18n==="function"){const i18n=resolveI18n(site,projectPath());if(i18n)return i18n}}catch{}return fallbackI18nFromSite(site)}async function loadStxSiteConfig(){const sitePath=projectPath("site.config.ts");if(!existsSync(sitePath))return{};try{const site=(await import(sitePath)).default;if(!site)return{};if(!site.i18n)return{site};const i18n=await resolveSiteI18n(site);return{site,i18n}}catch{}return{}}
|
package/dist/generate/images.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{awaitConfig}from"@stacksjs/config";import{log}from"@stacksjs/logging";import{projectPath}from"@stacksjs/path";export async function generateProjectImages(options={}){const images=(await awaitConfig()).images;if(!images||!images.social?.enabled&&!images.appStore?.enabled&&!images.appIcons?.enabled){log.debug("[generate:images] nothing declared in config/images.ts
|
|
1
|
+
import{awaitConfig}from"@stacksjs/config";import{log}from"@stacksjs/logging";import{projectPath}from"@stacksjs/path";export async function generateProjectImages(options={}){const images=(await awaitConfig()).images;if(!images||!images.social?.enabled&&!images.appStore?.enabled&&!images.appIcons?.enabled){log.debug("[generate:images] nothing declared in config/images.ts - skipping");return}const{countGeneratedImages,generateImages}=await import("@stacksjs/image"),result=await generateImages(images,{only:options.only,root:projectPath()}),counts=countGeneratedImages(result);if(options.verbose){for(const card of result.social)log.info(`[generate:images] ${card.path} \u2192 ${Object.values(card.files).join(", ")}`);for(const[display,paths]of Object.entries(result.appStore))log.info(`[generate:images] ${display} \u2192 ${paths.length} screenshot(s)`)}const summary=[counts.social?`${counts.social} social card(s)`:"",counts.appStore?`${counts.appStore} App Store screenshot(s)`:"",counts.appIcons?`${counts.appIcons} icon(s)`:""].filter(Boolean).join(", ");if(summary)log.success(`Generated ${summary}`);else log.info("[generate:images] nothing to generate")}
|
package/dist/generate/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import*as fs from"node:fs";import process from"node:process";import{runCommand}from"@stacksjs/cli";import{Action,NpmScript}from"@stacksjs/enums";import{log}from"@stacksjs/logging";import{frameworkPath,projectPath,storagePath}from"@stacksjs/path";import{ExitCode}from"@stacksjs/types";import{runNpmScript}from"@stacksjs/utils";import{runAction}from"../helpers";import{generateVsCodeCustomData as genVsCodeCustomData}from"../helpers/vscode-custom-data";import{generateProjectImages}from"./images";import{generateEnvFiles}from"./env-files";export{generateProjectImages}from"./images";export{generateVSchema}from"./vschema";export async function invoke(options){if(options?.types)await generateTypes(options);else if(options?.entries)await generateLibEntries(options);else if(options?.webTypes)await generateWebTypes(options);else if(options?.customData)await generateVsCodeCustomData();else if(options?.ideHelpers)await generateIdeHelpers(options);else if(options?.componentMeta)await generateComponentMeta();else if(options?.coreSymlink)await generateCoreSymlink();else if(options?.openApiSpec)await generateOpenApiSpec();else if(options?.images)await generateProjectImages({verbose:options?.verbose})}export function generate(options){return invoke(options)}export async function generateLibEntries(options){const result=await runAction(Action.GenerateLibraryEntries,{...options,cwd:projectPath()});if(result.isErr){log.error("There was an error generating your library entry points",result.error);process.exit(ExitCode.FatalError)}log.success("Library entry points generated successfully")}export async function generateWebTypes(options){const result=await runNpmScript(NpmScript.GenerateWebTypes,options);if(result.isErr){log.error("There was an error generating the web-types.json file.",result.error);process.exit(ExitCode.FatalError)}log.success("Successfully generated the web-types.json file")}export async function generateVsCodeCustomData(){const result=await genVsCodeCustomData();if(result.isErr){log.error("There was an error generating the custom-elements.json file.",result.error);process.exit(ExitCode.FatalError)}await runAction(Action.LintFix,{verbose:!0,cwd:projectPath()});log.success("Successfully generated the custom-elements.json file")}export async function generateIdeHelpers(options){const result=await runNpmScript(NpmScript.GenerateIdeHelpers,options);if(result.isErr){log.error("There was an error generating IDE helpers.",result.error);process.exit(ExitCode.FatalError)}await runAction(Action.LintFix,{verbose:!0,cwd:projectPath()});log.success("Successfully generated IDE helpers")}export async function generateComponentMeta(){const result=await genVsCodeCustomData();if(result.isErr){log.error("There was an error generating your component meta information.",result.error);process.exit(ExitCode.FatalError)}await runAction(Action.LintFix,{verbose:!0,cwd:projectPath()});log.success("Successfully generated component meta information")}export async function generateTypes(options){await generateEnvFiles();const entry=frameworkPath("core/actions/src/generate/types.ts");if(!fs.existsSync(entry)){await import(`./types?t=${Date.now()}`);return}const result=await runNpmScript(NpmScript.GenerateTypes,{cwd:frameworkPath(),...options});if(result.isErr){log.error("There was an error generating your types.",result.error);throw result.error instanceof Error?result.error:Error(String(result.error))}log.success("Types were generated successfully")}export async function watchTypes(options){try{const manifestPath=`${frameworkPath()}/package.json`,text=fs.existsSync(manifestPath)?fs.readFileSync(manifestPath,"utf-8"):"";if(!(text?JSON.parse(text):{})?.scripts?.["generate:types"]){log.warn("[generate:types --watch] no `generate:types` script in framework package.json
|
|
1
|
+
import*as fs from"node:fs";import process from"node:process";import{runCommand}from"@stacksjs/cli";import{Action,NpmScript}from"@stacksjs/enums";import{log}from"@stacksjs/logging";import{frameworkPath,projectPath,storagePath}from"@stacksjs/path";import{ExitCode}from"@stacksjs/types";import{runNpmScript}from"@stacksjs/utils";import{runAction}from"../helpers";import{generateVsCodeCustomData as genVsCodeCustomData}from"../helpers/vscode-custom-data";import{generateProjectImages}from"./images";import{generateEnvFiles}from"./env-files";export{generateProjectImages}from"./images";export{generateVSchema}from"./vschema";export async function invoke(options){if(options?.types)await generateTypes(options);else if(options?.entries)await generateLibEntries(options);else if(options?.webTypes)await generateWebTypes(options);else if(options?.customData)await generateVsCodeCustomData();else if(options?.ideHelpers)await generateIdeHelpers(options);else if(options?.componentMeta)await generateComponentMeta();else if(options?.coreSymlink)await generateCoreSymlink();else if(options?.openApiSpec)await generateOpenApiSpec();else if(options?.images)await generateProjectImages({verbose:options?.verbose})}export function generate(options){return invoke(options)}export async function generateLibEntries(options){const result=await runAction(Action.GenerateLibraryEntries,{...options,cwd:projectPath()});if(result.isErr){log.error("There was an error generating your library entry points",result.error);process.exit(ExitCode.FatalError)}log.success("Library entry points generated successfully")}export async function generateWebTypes(options){const result=await runNpmScript(NpmScript.GenerateWebTypes,options);if(result.isErr){log.error("There was an error generating the web-types.json file.",result.error);process.exit(ExitCode.FatalError)}log.success("Successfully generated the web-types.json file")}export async function generateVsCodeCustomData(){const result=await genVsCodeCustomData();if(result.isErr){log.error("There was an error generating the custom-elements.json file.",result.error);process.exit(ExitCode.FatalError)}await runAction(Action.LintFix,{verbose:!0,cwd:projectPath()});log.success("Successfully generated the custom-elements.json file")}export async function generateIdeHelpers(options){const result=await runNpmScript(NpmScript.GenerateIdeHelpers,options);if(result.isErr){log.error("There was an error generating IDE helpers.",result.error);process.exit(ExitCode.FatalError)}await runAction(Action.LintFix,{verbose:!0,cwd:projectPath()});log.success("Successfully generated IDE helpers")}export async function generateComponentMeta(){const result=await genVsCodeCustomData();if(result.isErr){log.error("There was an error generating your component meta information.",result.error);process.exit(ExitCode.FatalError)}await runAction(Action.LintFix,{verbose:!0,cwd:projectPath()});log.success("Successfully generated component meta information")}export async function generateTypes(options){await generateEnvFiles();const entry=frameworkPath("core/actions/src/generate/types.ts");if(!fs.existsSync(entry)){await import(`./types?t=${Date.now()}`);return}const result=await runNpmScript(NpmScript.GenerateTypes,{cwd:frameworkPath(),...options});if(result.isErr){log.error("There was an error generating your types.",result.error);throw result.error instanceof Error?result.error:Error(String(result.error))}log.success("Types were generated successfully")}export async function watchTypes(options){try{const manifestPath=`${frameworkPath()}/package.json`,text=fs.existsSync(manifestPath)?fs.readFileSync(manifestPath,"utf-8"):"";if(!(text?JSON.parse(text):{})?.scripts?.["generate:types"]){log.warn("[generate:types --watch] no `generate:types` script in framework package.json - type regeneration disabled. dev:api will keep running, but type definitions won't auto-refresh on model/config changes.");return}}catch{}const watched=[projectPath("app/Models"),storagePath("framework/defaults/app/Models"),projectPath("config"),projectPath("database/migrations")];log.info(`[generate:types --watch] watching ${watched.length} directories`);let pending=null,inflight=!1;const trigger=()=>{if(pending)clearTimeout(pending);pending=setTimeout(async()=>{if(inflight)return;inflight=!0;try{log.info("[generate:types --watch] change detected, regenerating\u2026");await generateTypes(options);log.success("[generate:types --watch] types up to date")}catch(err){log.error("[generate:types --watch] regeneration failed:",err)}finally{inflight=!1}},200)},watchers=[];for(const dir of watched)try{if(!fs.existsSync(dir))continue;const w=fs.watch(dir,{recursive:!0},()=>trigger());watchers.push(w)}catch(err){log.warn(`[generate:types --watch] cannot watch ${dir}: ${err}`)}if(watchers.length===0){log.warn("[generate:types --watch] no directories to watch - exiting");return}await new Promise((resolve)=>{process.once("SIGINT",()=>{log.info("[generate:types --watch] stopping");for(const w of watchers)try{w.close()}catch{}resolve()})})}export function generatePantryConfig(){log.success("Successfully generated `./pantry.yaml` based on your config")}export async function generateSeeder(){}export async function generateCoreSymlink(){const link=projectPath(".framework");if(fs.existsSync(link))await runCommand(`rm -f ${link}`);await runCommand(`ln -s ${frameworkPath()} ${link}`)}export async function generateOpenApiSpec(){const{generateOpenApi}=await import("@stacksjs/api");await generateOpenApi();log.success("Successfully generated Open API Spec")}
|
package/dist/make.js
CHANGED
|
@@ -21,4 +21,4 @@ export function ${factoryName}(): Partial<${modelName}Model> {
|
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
export default ${factoryName}
|
|
24
|
-
`;await writeOrPreview(factoryPath,content);log.success(`Created factory: ${factoryPath}`)}catch(error){log.error(`Failed to create factory ${factoryName}`,error);throw error}}export async function makeNotification(options){try{const name=options.name;log.info(`Creating your ${italic(name)} notification...`);await createNotification(options);log.success(`Created ${italic(name)} notification`)}catch(error){log.error("There was an error creating your notification",error);process.exit(ExitCode.FatalError)}}export async function createMail(options){const rawName=String(options.name||"").trim();if(!rawName){log.error("A mail name is required (e.g. `buddy make:mail OrderShipped`).");return!1}const className=pascalCase(rawName),kebabName=kebabCase(rawName),mailPath=p.userMailPath(`${className}.ts`),templatePath=p.userEmailsPath(`${kebabName}.stx`);if(!options.force){if(existsSync(mailPath)){console.error(`${italic(mailPath)} already exists
|
|
24
|
+
`;await writeOrPreview(factoryPath,content);log.success(`Created factory: ${factoryPath}`)}catch(error){log.error(`Failed to create factory ${factoryName}`,error);throw error}}export async function makeNotification(options){try{const name=options.name;log.info(`Creating your ${italic(name)} notification...`);await createNotification(options);log.success(`Created ${italic(name)} notification`)}catch(error){log.error("There was an error creating your notification",error);process.exit(ExitCode.FatalError)}}export async function createMail(options){const rawName=String(options.name||"").trim();if(!rawName){log.error("A mail name is required (e.g. `buddy make:mail OrderShipped`).");return!1}const className=pascalCase(rawName),kebabName=kebabCase(rawName),mailPath=p.userMailPath(`${className}.ts`),templatePath=p.userEmailsPath(`${kebabName}.stx`);if(!options.force){if(existsSync(mailPath)){console.error(`${italic(mailPath)} already exists - re-run with --force to overwrite.`);return!1}if(existsSync(templatePath)){console.error(`${italic(templatePath)} already exists - re-run with --force to overwrite.`);return!1}}if(!doesFolderExist(p.userMailPath()))await createFolder(p.userMailPath());if(!doesFolderExist(p.userEmailsPath()))await createFolder(p.userEmailsPath());try{await createFileWithTemplate(mailPath,"mail",className,kebabName);await createFileWithTemplate(templatePath,"mailTemplate",className,kebabName);return!0}catch(error){handleError("Error creating mailable",error);return!1}}export async function makeMail(options){try{const name=options.name;log.info(`Creating your ${italic(name)} mailable...`);if(!await createMail(options))process.exit(ExitCode.FatalError);log.success(`Created ${italic(pascalCase(name))} mailable + ${italic(kebabCase(name))}.stx template`)}catch(error){log.error("There was an error creating your mailable",error);process.exit(ExitCode.FatalError)}}export async function makePage(options){try{const name=options.name;log.info("Creating your page...");await createPage(options);log.success(`Created ${name} page`)}catch(error){log.error("There was an error creating your page",error);process.exit(ExitCode.FatalError)}}export async function createPage(options){const name=options.name;await createFileWithTemplate(p.userViewsPath(pageFileName(name)),"page",name)}export function pageFileName(name){return`${name}.stx`}export async function makeFunction(options){try{const name=options.name;log.info("Creating your function...");await createFunction(options);log.success(`Created ${name} function`)}catch(error){log.error("There was an error creating your function",error);process.exit(ExitCode.FatalError)}}export async function createFunction(options){const name=options.name;await createFileWithTemplate(p.userFunctionsPath(`${name}.ts`),"function",camelCase(name))}export async function makeLanguage(options){try{const name=options.name;log.info("Creating your translation file...");await createLanguage(options);log.success(`Created ${name} translation file`)}catch(error){log.error("There was an error creating your language.",error);process.exit(ExitCode.FatalError)}}export async function createLanguage(options){const name=options.name;await createFileWithTemplate(p.resourcesPath(`lang/${name}.yml`),"language",name)}export async function makeStack(options){try{const name=options.name;log.info(`Creating your ${name} stack...`);const stackDir=resolve(process.cwd(),name);if(doesFolderExist(stackDir)){log.error(`Directory "${name}" already exists`);process.exit(ExitCode.FatalError)}await createFolder(stackDir);const dirs=["app/Actions","app/Models","config","database/migrations","resources/views","resources/components","resources/functions","resources/partials","routes","public"];for(const dir of dirs)await createFolder(resolve(stackDir,dir));const shortName=name.replace(/^@[^/]+\//,"").replace(/-stack$/,"");await createFileWithTemplate(resolve(stackDir,"package.json"),"stackPackageJson",name,shortName);log.success(`Successfully scaffolded your "${name}" stack`);log.info("");log.info(` cd ${name}`);log.info(" # Add your models, actions, views, etc.");log.info(" # Push the repository, then submit its GitHub source to the Stacks registry.");log.info("");log.info(" Users install it with:");log.info(` buddy add ${shortName}`)}catch(error){log.error("There was an error creating your stack",error);process.exit(ExitCode.FatalError)}}export async function createNotification(options){const name=options.name;try{let importOption="EmailOptions";if(!doesFolderExist("notifications"))await createFolder("./notifications");if(options.chat)importOption="ChatOptions";if(options.sms)importOption="SMSOptions";await createFileWithTemplate(p.userNotificationsPath(`${name}.ts`),"notification",importOption);return!0}catch(error){handleError("Error creating notification",error);return!1}}export async function createMigration(options){const optionName=options.name;if(!optionName[0])throw Error("options.name is required and cannot be empty");const fileName=`${Date.now()}-${kebabCase(optionName)}.sql`,path=p.userMigrationsPath(fileName),snake=kebabCase(optionName).replace(/-/g,"_"),table=/^create_(.+)_table$/.exec(snake)?.[1]??"table_name",driver=(process.env.DB_CONNECTION||"sqlite").toLowerCase(),mysqlLike=driver==="mysql"||driver==="singlestore",tableIdentifier=mysqlLike?`\`${table}\``:`"${table}"`,primaryKey=driver==="postgres"?'"id" BIGSERIAL PRIMARY KEY':mysqlLike?"`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY":'"id" INTEGER PRIMARY KEY AUTOINCREMENT';try{await createFileWithTemplate(path,"migration",tableIdentifier,primaryKey);log.success(`Migration created: ${italic(`database/migrations/${fileName}`)}`)}catch(error){log.error(error)}}export async function makeQueueTable(){await runAction(Action.QueueTable)}export async function makeCertificate(){const domain=await localUrl();log.info("Creating SSL certificate...");await runCommand(`tlsx ${domain}`,{cwd:p.storagePath("keys")});log.success("Certificate created");log.info("Installing SSL certificate...");await runCommand("tlsx -install",{cwd:p.storagePath("keys")});log.success("Certificate installed")}export async function createModel(options){const optionName=options.name;if(!optionName[0])throw Error("options.name is required and cannot be empty");const name=optionName[0].toUpperCase()+optionName.slice(1),tableName=name.replace(/([A-Z])/g,"_$1").toLowerCase().replace(/^_/,"").replace(/([^s])$/,"$1s"),path=p.userModelsPath(`${name}.ts`);try{await createFileWithTemplate(path,"model",name,tableName);log.success(`Model created: ${italic(`app/Models/${name}.ts`)}`)}catch(error){log.error(error)}}export async function createMiddleware(options){const name=options.name;await createFileWithTemplate(p.userMiddlewarePath(`${name}.ts`),"middleware",name)}
|
package/dist/queue/work.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import process from"node:process";import{log}from"@stacksjs/logging";import{startProcessor,stopProcessor}from"@stacksjs/queue";process.on("unhandledRejection",(reason)=>{log.error(`Unhandled Rejection in queue worker: ${reason}`)});process.on("uncaughtException",(error)=>{log.error(`Uncaught Exception in queue worker: ${error.message}`)});const SHUTDOWN_GRACE_MS=Number(process.env.STACKS_QUEUE_SHUTDOWN_GRACE_MS)||1e4;let shuttingDown=!1;async function gracefulShutdown(signal){if(shuttingDown)return;shuttingDown=!0;log.info(`[queue] Worker received ${signal}, draining in-flight jobs (max ${SHUTDOWN_GRACE_MS/1000}s)\u2026`);setTimeout(()=>{log.warn("[queue] Drain window exceeded
|
|
1
|
+
import process from"node:process";import{log}from"@stacksjs/logging";import{startProcessor,stopProcessor}from"@stacksjs/queue";process.on("unhandledRejection",(reason)=>{log.error(`Unhandled Rejection in queue worker: ${reason}`)});process.on("uncaughtException",(error)=>{log.error(`Uncaught Exception in queue worker: ${error.message}`)});const SHUTDOWN_GRACE_MS=Number(process.env.STACKS_QUEUE_SHUTDOWN_GRACE_MS)||1e4;let shuttingDown=!1;async function gracefulShutdown(signal){if(shuttingDown)return;shuttingDown=!0;log.info(`[queue] Worker received ${signal}, draining in-flight jobs (max ${SHUTDOWN_GRACE_MS/1000}s)\u2026`);setTimeout(()=>{log.warn("[queue] Drain window exceeded - forcing worker exit.");process.exit(1)},SHUTDOWN_GRACE_MS+2000).unref();try{await stopProcessor({graceMs:SHUTDOWN_GRACE_MS})}catch(error){log.error("[queue] Error while draining worker on shutdown:",error)}process.exit(0)}process.on("SIGTERM",()=>{gracefulShutdown("SIGTERM")});process.on("SIGINT",()=>{gracefulShutdown("SIGINT")});const options=parseArgs(),queue=options.queue,concurrency=Number(options.concurrency)||1;log.info(`Starting queue worker with ${concurrency} concurrent worker(s)...`);const result=await startProcessor(queue,{concurrency});if(result.isErr){log.error("Failed to start queue worker:",result.error);process.exit(1)}log.success("Queue worker started successfully!");await new Promise(()=>{});function parseArgs(){const args={},argv=process.argv;for(let index=0;index<argv.length;index++){const arg=argv[index];if(!arg?.startsWith("--"))continue;const[key,inline]=arg.slice(2).split("=");if(!key)continue;if(inline!==void 0){args[key]=inline;continue}const next=argv[index+1];if(next!==void 0&&!next.startsWith("-")){args[key]=next;index++;continue}args[key]="true"}return args}
|
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
|
|
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,7 +1,7 @@
|
|
|
1
1
|
import{chmodSync,copyFileSync,cpSync,existsSync,mkdirSync,mkdtempSync,readFileSync,rmSync}from"node:fs";import{tmpdir}from"node:os";import{dirname,join}from"node:path";import process from"node:process";import{parseOptions}from"@stacksjs/cli";import{downloadTemplate}from"@stacksjs/gitit";import{path as p}from"@stacksjs/path";import{ExitCode}from"@stacksjs/types";import{buildTemplateString,detectLocalStacks,diffSnapshots,diffSnapshotsDetailed,MANAGED_PATHS,readChannel,readSyncedVersion,readVersion,resolveLatestStableTag,resolveSuccessMessage,resolveUpgradeContext,resolveUpgradeMessage,shouldAutoDetectLocalStacks,shouldCheckDirtyManagedPaths,shouldShortCircuit,snapshotTree,writeChannel,writeSyncedVersion}from"./framework-utils";import{detectDependencyInstaller,resolveDependencyRefreshCommand,resolvePantryExecutable,runPostSyncDependencyRefresh,runPostSyncMigration,shouldRefreshPostSyncDependencies,shouldRunPostSyncHooks}from"./framework-hooks";const options=parseOptions(),projectRoot=p.projectPath();if(!existsSync(p.projectPath("storage/framework/core"))){const{upgradeStacksPackages}=await import("./packages");await upgradeStacksPackages(projectRoot,{version:options.version,canary:options.canary,stable:options.stable,force:options.force,dryRun:options.dryRun,noPostinstall:options.noPostinstall??options.postinstall===!1})}const channelFile=join(projectRoot,".stacks-channel"),versionFile=join(projectRoot,".stacks-version"),currentChannel=readChannel(channelFile),corePkgPath=p.projectPath("storage/framework/core/buddy/package.json"),currentVersion=readVersion(corePkgPath),willResolveStable=!options.version&&!options.canary&&(!!options.stable||currentChannel!=="canary");let latestStableRef="";if(willResolveStable)latestStableRef=await resolveLatestStableTag()??(currentVersion?`v${currentVersion}`:"main");const ctx=resolveUpgradeContext(options,currentChannel,latestStableRef),explicitLocal=options.from?p.projectPath(options.from):null,detectedLocal=explicitLocal??(shouldAutoDetectLocalStacks(options)?detectLocalStacks(projectRoot):null),usingLocal=!!detectedLocal;console.log(resolveUpgradeMessage(ctx,currentChannel,!!options.stable));console.log(usingLocal?` source: local checkout at ${detectedLocal}`:` source: github:stacksjs/stacks#${ctx.ref}`);if(options.dryRun){await runDryRunPreview();process.exit(ExitCode.Success)}const alreadyRestarted=process.argv.includes("--__restarted");if(shouldCheckDirtyManagedPaths({force:!!options.force,alreadyRestarted})){const dirty=await detectDirtyManagedPaths(projectRoot);if(dirty.length>0){console.warn(`
|
|
2
2
|
\u26A0 The following framework-managed paths have uncommitted changes:`);for(const path of dirty)console.warn(` - ${path}`);console.warn(`
|
|
3
3
|
Aborting to avoid clobbering your edits.`);console.warn(` Re-run with --force to overwrite, or commit / stash the changes first.
|
|
4
|
-
`);process.exit(ExitCode.FatalError)}}let resolvedRemoteSha=null;if(!options.force&&!ctx.targetVersion){resolvedRemoteSha=await getRemoteSha(ctx,usingLocal,detectedLocal);const synced=readSyncedVersion(versionFile);if(shouldShortCircuit({force:!!options.force,targetVersion:ctx.targetVersion,remoteSha:resolvedRemoteSha,synced,channel:ctx.channel})){console.log(`\u2714 Already on the latest ${ctx.channel} (${resolvedRemoteSha.slice(0,7)}).`);process.exit(ExitCode.Success)}}const ownPath=p.frameworkPath("core/actions/src/upgrade/framework.ts"),ownHashBefore=await hashFileOrZero(ownPath),aggregate={added:0,changed:0,removed:0,unchanged:0},perPath=[];try{for(const managed of MANAGED_PATHS){const localTarget=join(projectRoot,managed.localPath),before=managed.isFile?await singleFileSnapshot(localTarget):await snapshotTree(localTarget,managed.skip);if(usingLocal&&detectedLocal)await syncFromLocal(detectedLocal,managed,localTarget);else{if(managed.isFile)continue;await syncFromGitHub(ctx.ref,managed,localTarget,!!options.force)}const after=managed.isFile?await singleFileSnapshot(localTarget):await snapshotTree(localTarget,managed.skip),summary=diffSnapshots(before,after);aggregate.added+=summary.added;aggregate.changed+=summary.changed;aggregate.removed+=summary.removed;aggregate.unchanged+=summary.unchanged;perPath.push({managed,summary})}if(!usingLocal)await syncRootFilesFromGitHub(ctx.ref)}catch(err){console.error("\u2718 Failed to upgrade Stacks framework:",err?.message||err);process.exit(ExitCode.FatalError)}const newVersion=readVersion(corePkgPath),ownHashAfter=await hashFileOrZero(ownPath);if(ownHashBefore!==0n&&ownHashAfter!==0n&&ownHashBefore!==ownHashAfter&&!alreadyRestarted){console.log(`Upgrade pulled a newer version of the upgrade action
|
|
4
|
+
`);process.exit(ExitCode.FatalError)}}let resolvedRemoteSha=null;if(!options.force&&!ctx.targetVersion){resolvedRemoteSha=await getRemoteSha(ctx,usingLocal,detectedLocal);const synced=readSyncedVersion(versionFile);if(shouldShortCircuit({force:!!options.force,targetVersion:ctx.targetVersion,remoteSha:resolvedRemoteSha,synced,channel:ctx.channel})){console.log(`\u2714 Already on the latest ${ctx.channel} (${resolvedRemoteSha.slice(0,7)}).`);process.exit(ExitCode.Success)}}const ownPath=p.frameworkPath("core/actions/src/upgrade/framework.ts"),ownHashBefore=await hashFileOrZero(ownPath),aggregate={added:0,changed:0,removed:0,unchanged:0},perPath=[];try{for(const managed of MANAGED_PATHS){const localTarget=join(projectRoot,managed.localPath),before=managed.isFile?await singleFileSnapshot(localTarget):await snapshotTree(localTarget,managed.skip);if(usingLocal&&detectedLocal)await syncFromLocal(detectedLocal,managed,localTarget);else{if(managed.isFile)continue;await syncFromGitHub(ctx.ref,managed,localTarget,!!options.force)}const after=managed.isFile?await singleFileSnapshot(localTarget):await snapshotTree(localTarget,managed.skip),summary=diffSnapshots(before,after);aggregate.added+=summary.added;aggregate.changed+=summary.changed;aggregate.removed+=summary.removed;aggregate.unchanged+=summary.unchanged;perPath.push({managed,summary})}if(!usingLocal)await syncRootFilesFromGitHub(ctx.ref)}catch(err){console.error("\u2718 Failed to upgrade Stacks framework:",err?.message||err);process.exit(ExitCode.FatalError)}const newVersion=readVersion(corePkgPath),ownHashAfter=await hashFileOrZero(ownPath);if(ownHashBefore!==0n&&ownHashAfter!==0n&&ownHashBefore!==ownHashAfter&&!alreadyRestarted){console.log(`Upgrade pulled a newer version of the upgrade action - re-running once to pick up new sync paths and hooks.
|
|
5
5
|
`);const args=process.argv.slice(2).filter((a)=>a!=="--__restarted"),code=await Bun.spawn({cmd:[process.argv[0]||"bun",ownPath,...args,"--__restarted"],cwd:projectRoot,stdout:"inherit",stderr:"inherit"}).exited;process.exit(code??0)}if(!ctx.targetVersion){writeChannel(channelFile,ctx.channel);if(!resolvedRemoteSha)resolvedRemoteSha=await getRemoteSha(ctx,usingLocal,detectedLocal);if(resolvedRemoteSha)writeSyncedVersion(versionFile,ctx.channel,resolvedRemoteSha)}console.log(`\u2714 ${resolveSuccessMessage(ctx,currentVersion,newVersion,currentChannel,!!options.force)}`);console.log("Sync summary:");for(const{managed,summary}of perPath)if(managed.isFile){const moved=summary.added+summary.changed>0;console.log(` ${managed.label.padEnd(10)} ${moved?"updated":"unchanged"}`)}else if(summary.added+summary.changed+summary.removed===0)console.log(` ${managed.label.padEnd(10)} unchanged (${summary.unchanged} files)`);else console.log(` ${managed.label.padEnd(10)} +${summary.added} ~${summary.changed} -${summary.removed} (${summary.unchanged} unchanged)`);const runHooks=!options.noPostinstall&&options.postinstall!==!1;if(runHooks)try{await runPostSyncHooks({aggregate,perPath,projectRoot,alreadyRestarted})}catch(err){console.error(`Post-sync hooks failed: ${err?.message||err}`);process.exit(ExitCode.FatalError)}if(ctx.channel==="canary"&&!ctx.targetVersion)console.warn("\u26A0 Canary builds may contain bugs or breaking changes. Not recommended for production.");process.exit(ExitCode.Success);async function runDryRunPreview(){console.log(`
|
|
6
6
|
DRY RUN - preview only. No files will be written, nothing will be installed, no migrations will run.
|
|
7
7
|
`);const dirty=await detectDirtyManagedPaths(projectRoot);if(dirty.length>0){console.warn(" Note: these framework-managed paths have uncommitted changes (a real run would abort without --force):");for(const path of dirty)console.warn(` - ${path}`);console.warn("")}let previewSha=null;if(!options.force&&!ctx.targetVersion){previewSha=await getRemoteSha(ctx,usingLocal,detectedLocal);const synced=readSyncedVersion(versionFile);if(shouldShortCircuit({force:!!options.force,targetVersion:ctx.targetVersion,remoteSha:previewSha,synced,channel:ctx.channel})){console.log(`\u2714 Already on the latest ${ctx.channel} (${previewSha.slice(0,7)}). A real run would change nothing.`);return}}const aggregate={added:0,changed:0,removed:0,unchanged:0},perPath=[];let sawRemoved=0,sourceVersion=null;const tmpBase=mkdtempSync(join(tmpdir(),"stacks-upgrade-dry-run-"));try{let repoTmp=null;for(const managed of MANAGED_PATHS){const localTarget=join(projectRoot,managed.localPath),before=managed.isFile?await singleFileSnapshot(localTarget):await snapshotTree(localTarget,managed.skip);let after;if(usingLocal&&detectedLocal){const src=join(detectedLocal,managed.subPath);if(!existsSync(src)){console.log(` ${managed.label.padEnd(10)} skip: source missing at ${src}`);continue}after=managed.isFile?await singleFileSnapshot(src):await snapshotTree(src,managed.skip)}else try{if(managed.isFile){if(!repoTmp){repoTmp=join(tmpBase,"repo");await downloadTemplate(`github:stacksjs/stacks#${ctx.ref}`,{dir:repoTmp,force:!0,forceClean:!0,preferOffline:!0,silent:!0})}after=await singleFileSnapshot(join(repoTmp,managed.subPath))}else{const dir=join(tmpBase,managed.label);await downloadTemplate(buildTemplateString(ctx.ref,managed.subPath),{dir,force:!0,forceClean:!0,preferOffline:!options.force,silent:!0});after=await snapshotTree(dir,managed.skip)}}catch(err){console.log(` ${managed.label.padEnd(10)} preview unavailable (${err?.message||err}); a real run would sync from github:stacksjs/stacks#${ctx.ref}`);continue}const summary=diffSnapshotsDetailed(before,after);aggregate.added+=summary.added;aggregate.changed+=summary.changed;aggregate.removed+=summary.removed;aggregate.unchanged+=summary.unchanged;perPath.push({managed,summary});sawRemoved+=summary.removed;if(managed.isFile){const moved=summary.added+summary.changed>0;console.log(` ${managed.label.padEnd(10)} ${moved?"would update":"unchanged"}`)}else if(summary.added+summary.changed+summary.removed===0)console.log(` ${managed.label.padEnd(10)} unchanged (${summary.unchanged} files)`);else{console.log(` ${managed.label.padEnd(10)} +${summary.added} ~${summary.changed} -${summary.removed} (${summary.unchanged} unchanged)`);const samples=[...summary.addedFiles.map((f)=>`+ ${f}`),...summary.changedFiles.map((f)=>`~ ${f}`)];for(const line of samples.slice(0,8))console.log(` ${line}`);if(samples.length>8)console.log(` ... and ${samples.length-8} more`)}}const sourcePkgPath=usingLocal&&detectedLocal?join(detectedLocal,"storage/framework/core/buddy/package.json"):repoTmp?join(repoTmp,"storage/framework/core/buddy/package.json"):join(tmpBase,"core","buddy","package.json");sourceVersion=readVersion(sourcePkgPath)}finally{rmSync(tmpBase,{recursive:!0,force:!0})}console.log("");if(currentVersion||sourceVersion)console.log(currentVersion===sourceVersion?` version: unchanged (v${currentVersion??"unknown"})`:` version: v${currentVersion??"unknown"} -> v${sourceVersion??"unknown"}`);console.log(`
|
|
@@ -9,4 +9,4 @@ import{chmodSync,copyFileSync,cpSync,existsSync,mkdirSync,mkdtempSync,readFileSy
|
|
|
9
9
|
(-N counts files no longer shipped upstream; the sync leaves them in place.)`);console.log(`
|
|
10
10
|
\u2714 Dry run complete. No changes were made.`)}async function detectDirtyManagedPaths(root){const gitDir=join(root,".git");if(!existsSync(gitDir))return[];try{const proc=Bun.spawn({cmd:["git","-C",root,"status","--porcelain","--",...MANAGED_PATHS.map((m)=>m.localPath)],stdout:"pipe",stderr:"pipe"}),output=await new Response(proc.stdout).text();await proc.exited;return output.split(`
|
|
11
11
|
`).filter((line)=>line.length>0).map((line)=>line.slice(3))}catch{return[]}}async function getRemoteSha(context,fromLocal,stacksRoot){const SHA_RE=/^[0-9a-f]{40}$/i;try{if(fromLocal&&stacksRoot){const proc=Bun.spawn({cmd:["git","-C",stacksRoot,"rev-parse",context.ref],stdout:"pipe",stderr:"pipe"}),out=(await new Response(proc.stdout).text()).trim();if(await proc.exited===0&&SHA_RE.test(out))return out.toLowerCase();const head=Bun.spawn({cmd:["git","-C",stacksRoot,"rev-parse","HEAD"],stdout:"pipe",stderr:"pipe"}),h=(await new Response(head.stdout).text()).trim();return await head.exited===0&&SHA_RE.test(h)?h.toLowerCase():null}const proc=Bun.spawn({cmd:["git","ls-remote","https://github.com/stacksjs/stacks.git",`refs/heads/${context.ref}`,`refs/tags/${context.ref}`,`refs/tags/${context.ref}^{}`],stdout:"pipe",stderr:"pipe"}),out=await new Response(proc.stdout).text();if(await proc.exited!==0)return null;const lines=out.split(`
|
|
12
|
-
`).filter(Boolean),m=(lines.find((l)=>l.includes("^{}"))??lines[0])?.match(/^([0-9a-f]{40})\s/i);return m?.[1]?m[1].toLowerCase():null}catch{return null}}async function hashFileOrZero(absPath){try{if(!existsSync(absPath))return 0n;const buf=await Bun.file(absPath).arrayBuffer();return Bun.hash(buf)}catch{return 0n}}async function singleFileSnapshot(absPath){const out=new Map;if(!existsSync(absPath))return out;try{const file=Bun.file(absPath);if(file.size>0){const buf=await file.arrayBuffer();out.set("self",{size:file.size,hash:Bun.hash(buf)})}}catch{}return out}async function syncFromLocal(stacksRoot,managed,localTarget){const src=join(stacksRoot,managed.subPath);if(!existsSync(src)){console.warn(` skip ${managed.label}: source missing at ${src}`);return}if(managed.isFile){mkdirSync(dirname(localTarget),{recursive:!0});copyFileSync(src,localTarget);if(managed.executable)chmodSync(localTarget,493);return}mkdirSync(localTarget,{recursive:!0});cpSync(src,localTarget,{recursive:!0,force:!0,filter:(source)=>{if(!managed.skip)return!0;const name=source.split("/").pop()||"";return!managed.skip.includes(name)}})}async function syncFromGitHub(ref,managed,localTarget,force){const template=buildTemplateString(ref,managed.subPath);await downloadTemplate(template,{dir:localTarget,force:!0,forceClean:!1,preferOffline:!force})}async function syncRootFilesFromGitHub(ref){const tmpDir=p.projectPath(".tmp-upgrade");try{await downloadTemplate(`github:stacksjs/stacks#${ref}`,{dir:tmpDir,force:!0,forceClean:!0,preferOffline:!0,silent:!0});for(const managed of MANAGED_PATHS){if(!managed.isFile)continue;const src=join(tmpDir,managed.subPath),dest=p.projectPath(managed.localPath);try{if(existsSync(src)&&existsSync(dest)){copyFileSync(src,dest);if(managed.executable)chmodSync(dest,493)}}catch{}}}catch{}finally{if(existsSync(tmpDir))rmSync(tmpDir,{recursive:!0,force:!0})}}async function runPostSyncHooks(args){const{aggregate,perPath,projectRoot,alreadyRestarted}=args,changeCount=aggregate.added+aggregate.changed+aggregate.removed;if(!shouldRunPostSyncHooks(changeCount,alreadyRestarted)){console.log("Nothing changed
|
|
12
|
+
`).filter(Boolean),m=(lines.find((l)=>l.includes("^{}"))??lines[0])?.match(/^([0-9a-f]{40})\s/i);return m?.[1]?m[1].toLowerCase():null}catch{return null}}async function hashFileOrZero(absPath){try{if(!existsSync(absPath))return 0n;const buf=await Bun.file(absPath).arrayBuffer();return Bun.hash(buf)}catch{return 0n}}async function singleFileSnapshot(absPath){const out=new Map;if(!existsSync(absPath))return out;try{const file=Bun.file(absPath);if(file.size>0){const buf=await file.arrayBuffer();out.set("self",{size:file.size,hash:Bun.hash(buf)})}}catch{}return out}async function syncFromLocal(stacksRoot,managed,localTarget){const src=join(stacksRoot,managed.subPath);if(!existsSync(src)){console.warn(` skip ${managed.label}: source missing at ${src}`);return}if(managed.isFile){mkdirSync(dirname(localTarget),{recursive:!0});copyFileSync(src,localTarget);if(managed.executable)chmodSync(localTarget,493);return}mkdirSync(localTarget,{recursive:!0});cpSync(src,localTarget,{recursive:!0,force:!0,filter:(source)=>{if(!managed.skip)return!0;const name=source.split("/").pop()||"";return!managed.skip.includes(name)}})}async function syncFromGitHub(ref,managed,localTarget,force){const template=buildTemplateString(ref,managed.subPath);await downloadTemplate(template,{dir:localTarget,force:!0,forceClean:!1,preferOffline:!force})}async function syncRootFilesFromGitHub(ref){const tmpDir=p.projectPath(".tmp-upgrade");try{await downloadTemplate(`github:stacksjs/stacks#${ref}`,{dir:tmpDir,force:!0,forceClean:!0,preferOffline:!0,silent:!0});for(const managed of MANAGED_PATHS){if(!managed.isFile)continue;const src=join(tmpDir,managed.subPath),dest=p.projectPath(managed.localPath);try{if(existsSync(src)&&existsSync(dest)){copyFileSync(src,dest);if(managed.executable)chmodSync(dest,493)}}catch{}}}catch{}finally{if(existsSync(tmpDir))rmSync(tmpDir,{recursive:!0,force:!0})}}async function runPostSyncHooks(args){const{aggregate,perPath,projectRoot,alreadyRestarted}=args,changeCount=aggregate.added+aggregate.changed+aggregate.removed;if(!shouldRunPostSyncHooks(changeCount,alreadyRestarted)){console.log("Nothing changed - skipping post-sync hooks.");return}console.log("Regenerating auto-import manifest...");try{const{generateAutoImportFiles}=await import("@stacksjs/server");await generateAutoImportFiles()}catch(err){console.warn(` auto-imports failed (non-fatal): ${err?.message||err}`)}if(shouldRefreshPostSyncDependencies(perPath.some((entry)=>{const{managed,summary}=entry;if(managed.isFile)return!1;if(managed.label!=="core"&&managed.label!=="defaults")return!1;if(summary.added+summary.changed===0)return!1;return pkgJsonInTree(join(projectRoot,managed.localPath))}),alreadyRestarted)){const installer=detectDependencyInstaller(existsSync(join(projectRoot,"pantry.lock"))),cmd=resolveDependencyRefreshCommand({installer,bunExecutable:process.argv[0]||"bun",pantryExecutable:resolvePantryExecutable(pantryExecutableCandidates(projectRoot),existsSync)});console.log(`Running \`${installer} install\` to refresh dependencies...`);try{await runPostSyncDependencyRefresh({cmd,projectRoot,spawn:(spawnOptions)=>Bun.spawn(spawnOptions)})}catch(err){console.warn(` ${installer} install failed (non-fatal): ${err?.message||err}`)}}const migrationsDir=join(projectRoot,"database","migrations");if(existsSync(migrationsDir)){console.log("Running pending migrations...");const migrateScript=p.frameworkPath("core/buddy/src/cli.ts");await runPostSyncMigration({bunExecutable:process.argv[0]||"bun",migrateScript,projectRoot,spawn:(spawnOptions)=>Bun.spawn(spawnOptions)})}}function pantryExecutableCandidates(projectRoot){const home=process.env.HOME??"";return[join(projectRoot,"pantry",".bin","pantry"),home?join(home,".local","bin","pantry"):"",home?join(home,".local","share","pantry","global","bin","pantry"):""].filter(Boolean)}function pkgJsonInTree(dir){try{if(!existsSync(dir))return!1;return readFileSync(join(dir,"package.json"),"utf-8").length>0}catch{return existsSync(dir)}}
|
package/dist/upgrade/packages.js
CHANGED
|
@@ -2,7 +2,7 @@ import{existsSync,readdirSync,readFileSync,statSync,writeFileSync}from"node:fs";
|
|
|
2
2
|
Standalone package detected. No Stacks framework source is installed.`);if(options.dryRun){console.log(" --dry-run: would refresh the package dependencies with `bun update`. No files were changed.\n");process.exit(0)}if(options.noPostinstall){console.log(" --no-postinstall: skipping the dependency refresh. Run `bun update` when ready.\n");process.exit(0)}console.log(` Refreshing declared dependencies...
|
|
3
3
|
`);if((await runCommand(standalonePackageUpdateCommand(),{cwd:projectRoot})).isErr){console.error("\n\u2717 The dependency refresh failed. Resolve the reported error and re-run `buddy update`.\n");process.exit(1)}console.log(`
|
|
4
4
|
\u2714 Standalone package dependencies are up to date.
|
|
5
|
-
`);process.exit(0)}async function resolveTarget(options){const res=await fetch("https://registry.npmjs.org/stacks").catch(()=>null);if(!res||!res.ok)throw Error("Could not reach the npm registry to resolve the target `stacks` version.");const meta=await res.json(),distTags=meta["dist-tags"]??{},versions=meta.versions??{};let version;if(options.version){if(!versions[options.version])throw Error(`stacks@${options.version} was not found on the npm registry.`);version=options.version}else{const tag=options.canary?"canary":"latest",resolved=distTags[tag];if(!resolved)throw Error(`The \`${tag}\` dist-tag is not published for \`stacks\`.`);version=resolved}const metaDeps=versions[version]?.dependencies??{};return{version,metaDeps}}export function baseVersion(range){return range.replace(/^[\^~>=<\s]+/,"").trim()}export function lockstepPackages(metaDeps,target){const lockstep=new Set(["stacks"]);for(const[name,range]of Object.entries(metaDeps))if(name.startsWith("@stacksjs/")&&baseVersion(range)===target)lockstep.add(name);return lockstep}export function applyBumps(pkg,target,lockstep){const changes=[];for(const field of["dependencies","devDependencies"]){const deps=pkg[field];if(!deps)continue;for(const[name,spec]of Object.entries(deps)){if(!lockstep.has(name))continue;const next=`${/^[\^~]/.test(spec)?spec[0]:/^\d/.test(spec)?"":"^"}${target}`;if(spec!==next){changes.push({name,from:spec,to:next});deps[name]=next}}}return changes}export async function upgradeStacksPackages(projectRoot,options){const pkgPath=join(projectRoot,"package.json");if(!existsSync(pkgPath)){console.error("No package.json found
|
|
5
|
+
`);process.exit(0)}async function resolveTarget(options){const res=await fetch("https://registry.npmjs.org/stacks").catch(()=>null);if(!res||!res.ok)throw Error("Could not reach the npm registry to resolve the target `stacks` version.");const meta=await res.json(),distTags=meta["dist-tags"]??{},versions=meta.versions??{};let version;if(options.version){if(!versions[options.version])throw Error(`stacks@${options.version} was not found on the npm registry.`);version=options.version}else{const tag=options.canary?"canary":"latest",resolved=distTags[tag];if(!resolved)throw Error(`The \`${tag}\` dist-tag is not published for \`stacks\`.`);version=resolved}const metaDeps=versions[version]?.dependencies??{};return{version,metaDeps}}export function baseVersion(range){return range.replace(/^[\^~>=<\s]+/,"").trim()}export function lockstepPackages(metaDeps,target){const lockstep=new Set(["stacks"]);for(const[name,range]of Object.entries(metaDeps))if(name.startsWith("@stacksjs/")&&baseVersion(range)===target)lockstep.add(name);return lockstep}export function applyBumps(pkg,target,lockstep){const changes=[];for(const field of["dependencies","devDependencies"]){const deps=pkg[field];if(!deps)continue;for(const[name,spec]of Object.entries(deps)){if(!lockstep.has(name))continue;const next=`${/^[\^~]/.test(spec)?spec[0]:/^\d/.test(spec)?"":"^"}${target}`;if(spec!==next){changes.push({name,from:spec,to:next});deps[name]=next}}}return changes}export async function upgradeStacksPackages(projectRoot,options){const pkgPath=join(projectRoot,"package.json");if(!existsSync(pkgPath)){console.error("No package.json found - nothing to upgrade.");process.exit(1)}const raw=readFileSync(pkgPath,"utf-8"),pkg=JSON.parse(raw),current=pkg.dependencies?.stacks??pkg.devDependencies?.stacks;if(!hasStacksDependency(pkg))return updateStandalonePackage(projectRoot,options);const{version:target,metaDeps}=await resolveTarget(options).catch((err)=>{console.error(`\u2717 ${err.message}`);process.exit(1)});console.log(`
|
|
6
6
|
Upgrading the Stacks framework${options.canary?" (canary)":""} \u2192 ${target}`);if(current)console.log(` current \`stacks\` constraint: ${current}
|
|
7
7
|
`);const lockstep=lockstepPackages(metaDeps,target),changes=applyBumps(pkg,target,lockstep),projectManifestChanges=migratePackageProjectManifest(pkg),manifestChanges=reconcileVendoredManifests(projectRoot,target,{dryRun:options.dryRun,context:{lockstep,metaDeps}});if(changes.length>0){console.log(" The following dependencies will be updated:");const width=Math.max(...changes.map((c)=>c.name.length));for(const c of changes)console.log(` ${c.name.padEnd(width)} ${c.from} \u2192 ${c.to}`);console.log("")}if(manifestChanges.length>0){console.log(` Vendored manifests under storage/framework (${manifestChanges.length}):`);const width=Math.max(...manifestChanges.map((c)=>c.name.length));for(const c of manifestChanges)console.log(` ${c.name.padEnd(width)} ${c.from} \u2192 ${c.to} ${c.file}`);console.log("")}if(projectManifestChanges.length>0)console.log(` Project manifest migrations: ${projectManifestChanges.length}
|
|
8
8
|
`);if(options.dryRun){const pending=measureDefaultsDrift(projectRoot);if(pending&&pending.length>0){const installed=installedDefaultsVersion(projectRoot);console.log(` Managed project files, against the installed @stacksjs/defaults${installed?` (${installed})`:""}:`);console.log(` ${summarizeStructureChanges(pending)} in storage/framework/defaults and project support files
|
|
@@ -11,10 +11,10 @@ import{existsSync,readdirSync,readFileSync,statSync,writeFileSync}from"node:fs";
|
|
|
11
11
|
`)?`
|
|
12
12
|
`:"";writeFileSync(pkgPath,`${JSON.stringify(pkg,null,2)}${trailing}`);console.log("\u2714 Updated package.json")}if(options.noPostinstall){console.log(" --no-postinstall: skipping install. Run this to pull the new versions:");console.log(` ${frameworkPackageUpdateCommand()}
|
|
13
13
|
`);process.exit(0)}if(changes.length>0||manifestChanges.length>0||projectManifestChanges.length>0||options.force){console.log(` Installing\u2026
|
|
14
|
-
`);if((await runCommand(frameworkPackageUpdateCommand(),{cwd:projectRoot})).isErr){console.error("\n\u2717 The install step failed. Your package.json was updated
|
|
14
|
+
`);if((await runCommand(frameworkPackageUpdateCommand(),{cwd:projectRoot})).isErr){console.error("\n\u2717 The install step failed. Your package.json was updated - resolve the error and re-run `bun update`.\n");process.exit(1)}}const defaultsPackageRoot=join(projectRoot,"node_modules/@stacksjs/defaults"),structureChanges=existsSync(join(defaultsPackageRoot,"package.json"))?syncPackageProjectFiles(projectRoot,defaultsPackageRoot):[],tsconfigChanges=migratePackageProjectTsconfig(projectRoot);if(structureChanges.length>0||tsconfigChanges.length>0){const added=structureChanges.filter((change)=>change.action==="add").length,updated=structureChanges.filter((change)=>change.action==="update").length+tsconfigChanges.length,removed=structureChanges.filter((change)=>change.action==="remove").length;console.log(` Project structure: +${added} ~${updated} -${removed}`)}for(const provider of detectProjectAiProviders(projectRoot))if((await runCommand(`./buddy setup:ai ${provider} --force`,{cwd:projectRoot})).isErr){console.error(`
|
|
15
15
|
\u2717 AI setup refresh failed for ${provider}. Re-run \`buddy setup:ai ${provider} --force\`.
|
|
16
16
|
`);process.exit(1)}if(!(changes.length>0||manifestChanges.length>0||projectManifestChanges.length>0||structureChanges.length>0||tsconfigChanges.length>0)&&!options.force)console.log(`
|
|
17
|
-
\u2714 Already up to date
|
|
17
|
+
\u2714 Already up to date - dependencies and managed project files match the target.
|
|
18
18
|
`);else console.log(`
|
|
19
19
|
\u2714 Upgraded to stacks@${target}. Review the changelog: https://github.com/stacksjs/stacks/releases/tag/v${target}
|
|
20
20
|
`);process.exit(0)}export function resolveManifestSpec(name,spec,target,context){if(name!=="stacks"&&!name.startsWith("@stacksjs/"))return null;if(context&&!context.lockstep.has(name)){const declared=context.metaDeps[name];if(!declared)return null;return baseVersion(declared)===baseVersion(spec)?null:declared}if(spec.startsWith("workspace:"))return`^${target}`;if(baseVersion(spec)===target)return null;if(!/^[\^~]?\d/.test(spec))return null;return`${/^[\^~]/.test(spec)?spec[0]:""}${target}`}export function reconcileVendoredManifests(projectRoot,target,options={}){const root=join(projectRoot,"storage/framework");if(!existsSync(root))return[];const changes=[],walk=(dir)=>{let entries;try{entries=readdirSync(dir)}catch{return}for(const entry of entries){if(entry==="node_modules"||entry==="dist"||entry===".git")continue;const full=join(dir,entry);let stat;try{stat=statSync(full)}catch{continue}if(stat.isDirectory()){walk(full);continue}if(entry!=="package.json")continue;let raw;try{raw=readFileSync(full,"utf-8")}catch{continue}let pkg;try{pkg=JSON.parse(raw)}catch{continue}let touched=!1;for(const field of["dependencies","devDependencies"]){const deps=pkg[field];if(!deps)continue;for(const[name,spec]of Object.entries(deps)){const next=resolveManifestSpec(name,spec,target,options.context);if(!next)continue;changes.push({file:relative(projectRoot,full),name,from:spec,to:next});deps[name]=next;touched=!0}}if(touched&&!options.dryRun){const suffix=raw.endsWith(`
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@stacksjs/actions",
|
|
3
3
|
"type": "module",
|
|
4
4
|
"sideEffects": false,
|
|
5
|
-
"version": "0.72.
|
|
5
|
+
"version": "0.72.39",
|
|
6
6
|
"description": "The Stacks actions.",
|
|
7
7
|
"author": "Chris Breuer",
|
|
8
8
|
"contributors": [
|
|
@@ -59,35 +59,35 @@
|
|
|
59
59
|
"prepublishOnly": "bun run build"
|
|
60
60
|
},
|
|
61
61
|
"dependencies": {
|
|
62
|
-
"@stacksjs/config": "0.72.
|
|
62
|
+
"@stacksjs/config": "0.72.39",
|
|
63
63
|
"@stacksjs/bumpx": "^0.2.6",
|
|
64
64
|
"@stacksjs/bunpress": "^0.2.8",
|
|
65
65
|
"@stacksjs/logsmith": "^0.2.3",
|
|
66
|
-
"@stacksjs/registry": "0.72.
|
|
66
|
+
"@stacksjs/registry": "0.72.39",
|
|
67
67
|
"@stacksjs/stx": "^0.2.197",
|
|
68
68
|
"@stacksjs/ts-cloud": "^0.9.4",
|
|
69
69
|
"@stacksjs/ts-md": "^0.1.1"
|
|
70
70
|
},
|
|
71
71
|
"devDependencies": {
|
|
72
|
-
"@stacksjs/api": "0.72.
|
|
73
|
-
"@stacksjs/cli": "0.72.
|
|
74
|
-
"@stacksjs/database": "0.72.
|
|
72
|
+
"@stacksjs/api": "0.72.39",
|
|
73
|
+
"@stacksjs/cli": "0.72.39",
|
|
74
|
+
"@stacksjs/database": "0.72.39",
|
|
75
75
|
"@stacksjs/tlsx": "^0.13.2",
|
|
76
76
|
"better-dx": "^0.2.24",
|
|
77
|
-
"@stacksjs/dns": "0.72.
|
|
78
|
-
"@stacksjs/enums": "0.72.
|
|
79
|
-
"@stacksjs/env": "0.72.
|
|
80
|
-
"@stacksjs/error-handling": "0.72.
|
|
81
|
-
"@stacksjs/image": "0.72.
|
|
82
|
-
"@stacksjs/logging": "0.72.
|
|
83
|
-
"@stacksjs/path": "0.72.
|
|
84
|
-
"@stacksjs/security": "0.72.
|
|
85
|
-
"@stacksjs/cms": "0.72.
|
|
86
|
-
"@stacksjs/sites": "0.72.
|
|
87
|
-
"@stacksjs/storage": "0.72.
|
|
88
|
-
"@stacksjs/strings": "0.72.
|
|
89
|
-
"@stacksjs/utils": "0.72.
|
|
90
|
-
"@stacksjs/validation": "0.72.
|
|
77
|
+
"@stacksjs/dns": "0.72.39",
|
|
78
|
+
"@stacksjs/enums": "0.72.39",
|
|
79
|
+
"@stacksjs/env": "0.72.39",
|
|
80
|
+
"@stacksjs/error-handling": "0.72.39",
|
|
81
|
+
"@stacksjs/image": "0.72.39",
|
|
82
|
+
"@stacksjs/logging": "0.72.39",
|
|
83
|
+
"@stacksjs/path": "0.72.39",
|
|
84
|
+
"@stacksjs/security": "0.72.39",
|
|
85
|
+
"@stacksjs/cms": "0.72.39",
|
|
86
|
+
"@stacksjs/sites": "0.72.39",
|
|
87
|
+
"@stacksjs/storage": "0.72.39",
|
|
88
|
+
"@stacksjs/strings": "0.72.39",
|
|
89
|
+
"@stacksjs/utils": "0.72.39",
|
|
90
|
+
"@stacksjs/validation": "0.72.39"
|
|
91
91
|
},
|
|
92
92
|
"peerDependencies": {
|
|
93
93
|
"pickier": "^0.1.58",
|