@stacksjs/actions 0.75.12 → 0.75.14
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/index.d.ts +2 -0
- package/dist/index.js +1 -1
- package/dist/upgrade/framework-utils.js +1 -1
- package/dist/upgrade/framework.js +4 -3
- package/dist/upgrade/package-project.d.ts +27 -0
- package/dist/upgrade/package-project.js +1 -1
- package/dist/upgrade/packages.js +7 -5
- package/dist/upgrade/route-actions.d.ts +38 -0
- package/dist/upgrade/route-actions.js +5 -0
- package/package.json +41 -41
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export type { StxLintConfig, StxLintReport, StxLintResult } from './lint/stx-gate';
|
|
2
|
+
export type { RouteActionReference } from './upgrade/route-actions';
|
|
2
3
|
export type { ProjectStructureChange } from './upgrade/package-project';
|
|
3
4
|
export type { CrudField } from './scaffold-crud';
|
|
4
5
|
export type { DiscoveredPackagesManifest, PackageStacksMeta } from './discover-packages';
|
|
@@ -39,6 +40,7 @@ export { DEFAULT_STX_LINT_CONFIG, loadStxLintConfig, runStxLint } from './lint/s
|
|
|
39
40
|
// import. The cheap version-level check lives in @stacksjs/path, because the
|
|
40
41
|
// boot path consults it and cannot depend on this package.
|
|
41
42
|
export { measureDefaultsDrift, summarizeStructureChanges } from './upgrade/package-project';
|
|
43
|
+
export { describeDanglingRouteActions, findDanglingRouteActions } from './upgrade/route-actions';
|
|
42
44
|
export * from './setup/index';
|
|
43
45
|
// makeFactory,
|
|
44
46
|
export {
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{setActionRunner}from"@stacksjs/action-runner";import{runAction}from"./helpers";setActionRunner((action,options)=>runAction(action,options));export*from"./action";export{add as runAdd}from"./add";export{BlogAdminError,deleteBlogPost,getBlogPost,listBlogPosts,saveBlogPost,slugify as blogSlugify}from"./blog-admin";export{commit as runCommit}from"./commit";export*from"./dev";export*from"./generate";export*from"./helpers";export*from"./library";export{formatProject,lintFix,lintProject}from"./lint/lint";export{DEFAULT_STX_LINT_CONFIG,loadStxLintConfig,runStxLint}from"./lint/stx-gate";export{measureDefaultsDrift,summarizeStructureChanges}from"./upgrade/package-project";export*from"./setup";export{createComponent,createDatabase,createFactory,createFunction,createLanguage,createMiddleware,createMigration,createModel,createMail,createNotification,createPage,isDryRunActive,makeAction,makeCertificate,makeComponent,makeDatabase,makeFunction,makeLanguage,makeMail,makeNotification,makePage,makeQueueTable,makeStack,make as runMake,setDryRun}from"./make";export{discoverPackages,ensureDiscoveredPackages}from"./discover-packages";export{parseFields,scaffoldCrud}from"./scaffold-crud";export{installStack,uninstallStack,listStacks}from"./stacks";export{makeJob}from"./make-job";export{makeCommand}from"./make-command";export{makePolicy}from"./make-policy";export{makeResource}from"./make-resource";
|
|
1
|
+
import{setActionRunner}from"@stacksjs/action-runner";import{runAction}from"./helpers";setActionRunner((action,options)=>runAction(action,options));export*from"./action";export{add as runAdd}from"./add";export{BlogAdminError,deleteBlogPost,getBlogPost,listBlogPosts,saveBlogPost,slugify as blogSlugify}from"./blog-admin";export{commit as runCommit}from"./commit";export*from"./dev";export*from"./generate";export*from"./helpers";export*from"./library";export{formatProject,lintFix,lintProject}from"./lint/lint";export{DEFAULT_STX_LINT_CONFIG,loadStxLintConfig,runStxLint}from"./lint/stx-gate";export{measureDefaultsDrift,summarizeStructureChanges}from"./upgrade/package-project";export{describeDanglingRouteActions,findDanglingRouteActions}from"./upgrade/route-actions";export*from"./setup";export{createComponent,createDatabase,createFactory,createFunction,createLanguage,createMiddleware,createMigration,createModel,createMail,createNotification,createPage,isDryRunActive,makeAction,makeCertificate,makeComponent,makeDatabase,makeFunction,makeLanguage,makeMail,makeNotification,makePage,makeQueueTable,makeStack,make as runMake,setDryRun}from"./make";export{discoverPackages,ensureDiscoveredPackages}from"./discover-packages";export{parseFields,scaffoldCrud}from"./scaffold-crud";export{installStack,uninstallStack,listStacks}from"./stacks";export{makeJob}from"./make-job";export{makeCommand}from"./make-command";export{makePolicy}from"./make-policy";export{makeResource}from"./make-resource";
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import{existsSync,readdirSync,readFileSync,statSync,writeFileSync}from"node:fs";import{homedir}from"node:os";import{join,resolve}from"node:path";import process from"node:process";export function resolveUpgradeContext(options,currentChannel,latestStableRef){const targetVersion=options.version;if(targetVersion)return{channel:"stable",ref:targetVersion.startsWith("v")?targetVersion:`v${targetVersion}`,targetVersion};if(options.canary)return{channel:"canary",ref:"main"};if(options.stable)return{channel:"stable",ref:latestStableRef};if(currentChannel==="canary")return{channel:"canary",ref:"main"};return{channel:"stable",ref:latestStableRef}}function compareVersionParts(a,b){return a[0]-b[0]||a[1]-b[1]||a[2]-b[2]}export function pickLatestStableTag(tags){const re=/^v(\d+)\.(\d+)\.(\d+)$/;let best=null;for(const tag of tags){const m=re.exec(tag.trim());if(!m)continue;const parts=[Number(m[1]),Number(m[2]),Number(m[3])];if(!best||compareVersionParts(parts,best.parts)>0)best={tag:tag.trim(),parts}}return best?.tag??null}export async function resolveLatestStableTag(){try{const proc=Bun.spawn({cmd:["git","ls-remote","--tags","--refs","https://github.com/stacksjs/stacks.git","v*"],stdout:"pipe",stderr:"pipe"}),out=await new Response(proc.stdout).text();if(await proc.exited!==0)return null;const tags=out.split(`
|
|
2
|
-
`).map((line)=>line.split("\t")[1]?.replace("refs/tags/","")).filter((t)=>!!t);return pickLatestStableTag(tags)}catch{return null}}export function buildTemplateString(ref,subPath="storage/framework/core"){return`github:stacksjs/stacks/${subPath}#${ref}`}export function readVersion(pkgPath){try{if(existsSync(pkgPath))return JSON.parse(readFileSync(pkgPath,"utf-8")).version||null}catch{}return null}export function readChannel(channelFilePath){try{if(existsSync(channelFilePath)){if(readFileSync(channelFilePath,"utf-8").trim()==="canary")return"canary"}}catch{}return"stable"}export function writeChannel(channelFilePath,ch){try{writeFileSync(channelFilePath,ch,"utf-8")}catch{}}export function readSyncedVersion(versionFilePath){try{if(existsSync(versionFilePath)){const content=readFileSync(versionFilePath,"utf-8").trim(),[ch,sha]=content.split(/\s+/);if((ch==="stable"||ch==="canary")&&sha&&/^[0-9a-f]{40}$/i.test(sha))return{channel:ch,sha:sha.toLowerCase()}}}catch{}return null}export function writeSyncedVersion(versionFilePath,ch,sha){try{writeFileSync(versionFilePath,`${ch} ${sha}`,"utf-8")}catch{}}export function shouldCheckDirtyManagedPaths(args){return!args.force&&!args.alreadyRestarted}export function shouldShortCircuit(args){const{force,targetVersion,remoteSha,synced,channel}=args;if(force||targetVersion)return!1;if(!remoteSha||!synced)return!1;return synced.channel===channel&&synced.sha===remoteSha.toLowerCase()}export function resolveUpgradeMessage(ctx,currentChannel,isStableFlag){if(ctx.targetVersion)return`Installing Stacks v${ctx.targetVersion.replace(/^v/,"")}...`;if(ctx.channel==="canary")return"Upgrading to the latest canary build...";if(isStableFlag&¤tChannel==="canary")return"Switching back to the latest stable release...";return"Upgrading to the latest version..."}export function resolveSuccessMessage(ctx,currentVersion,newVersion,currentChannel,force){if(ctx.targetVersion){const pinned=ctx.targetVersion.replace(/^v/,"");if(currentVersion===pinned&&!force)return`Stacks is already at v${pinned}`;if(currentVersion)return`Installed Stacks v${pinned} (was v${currentVersion})`;return`Installed Stacks v${pinned}`}if(currentVersion&&newVersion&¤tVersion===newVersion&&!force){if(ctx.channel==="canary")return`Stacks is already up to date (canary ${currentVersion})`;return`Stacks is already up to date (v${currentVersion})`}if(currentVersion&&newVersion){const from=currentChannel==="canary"?`canary ${currentVersion}`:`v${currentVersion}`,to=ctx.channel==="canary"?`canary ${newVersion}`:`v${newVersion}`;return`Upgraded Stacks from ${from} to ${to}`}if(newVersion)return`Upgraded Stacks to ${ctx.channel==="canary"?`canary ${newVersion}`:`v${newVersion}`}`;return`Upgraded Stacks to ${ctx.ref} (latest)`}export const MANAGED_PATHS=[{localPath:"storage/framework/core",subPath:"storage/framework/core",label:"core",skip:["node_modules","dist","bun.lock",".DS_Store"]},{localPath:"storage/framework/defaults",subPath:"storage/framework/defaults",label:"defaults",skip:["node_modules","dist",".DS_Store",".discovered-models.json"]},{localPath:"buddy",subPath:"buddy",label:"buddy",isFile:!0,executable:!0},{localPath:"bootstrap",subPath:"bootstrap",label:"bootstrap",isFile:!0,executable:!0}];export function detectLocalStacks(projectRoot){const candidates=[process.env.STACKS_LOCAL_PATH,resolve(projectRoot,"..","stacks"),resolve(projectRoot,"..","..","stacks"),resolve(homedir(),"Code","stacks"),resolve(homedir(),"code","stacks"),resolve(homedir(),"projects","stacks"),resolve(homedir(),"src","stacks")];for(const candidate of candidates){if(!candidate)continue;const marker=join(candidate,"storage","framework","core","buddy","package.json");try{if(existsSync(marker)&&resolve(candidate)!==resolve(projectRoot))return resolve(candidate)}catch{}}return null}export function shouldAutoDetectLocalStacks(options){return!options.from&&!options.version}export async function snapshotTree(root,skip=[]){const out=new Map;if(!existsSync(root))return out;const skipSet=new Set(skip),stack=[{abs:root,rel:""}],hashJobs=[];while(stack.length>0){const{abs,rel}=stack.pop();let entries;try{entries=readdirSync(abs)}catch{continue}for(const name of entries){if(skipSet.has(name))continue;const childAbs=join(abs,name),childRel=rel?`${rel}/${name}`:name;let info;try{info=statSync(childAbs)}catch{continue}if(info.isDirectory())stack.push({abs:childAbs,rel:childRel});else if(info.isFile()){const size=info.size;hashJobs.push((async()=>{const hash=await hashFile(childAbs);out.set(childRel,{size,hash})})())}}}await Promise.all(hashJobs);return out}export function diffSnapshots(before,after){let added=0,changed=0,unchanged=0;for(const[rel,info]of after){const prev=before.get(rel);if(!prev)added++;else if(prev.size!==info.size||prev.hash!==info.hash)changed++;else unchanged++}let removed=0;for(const rel of before.keys())if(!after.has(rel))removed++;return{added,changed,removed,unchanged}}export function diffSnapshotsDetailed(before,after){const addedFiles=[],changedFiles=[],removedFiles=[];let unchanged=0;for(const[rel,info]of after){const prev=before.get(rel);if(!prev)addedFiles.push(rel);else if(prev.size!==info.size||prev.hash!==info.hash)changedFiles.push(rel);else unchanged++}for(const rel of before.keys())if(!after.has(rel))removedFiles.push(rel);addedFiles.sort();changedFiles.sort();removedFiles.sort();return{added:addedFiles.length,changed:changedFiles.length,removed:removedFiles.length,unchanged,addedFiles,changedFiles,removedFiles}}async function hashFile(absPath){try{const buf=await Bun.file(absPath).arrayBuffer();return Bun.hash(buf)}catch{return 0n}}
|
|
2
|
+
`).map((line)=>line.split("\t")[1]?.replace("refs/tags/","")).filter((t)=>!!t);return pickLatestStableTag(tags)}catch{return null}}export function buildTemplateString(ref,subPath="storage/framework/core"){return`github:stacksjs/stacks/${subPath}#${ref}`}export function readVersion(pkgPath){try{if(existsSync(pkgPath))return JSON.parse(readFileSync(pkgPath,"utf-8")).version||null}catch{}return null}export function readChannel(channelFilePath){try{if(existsSync(channelFilePath)){if(readFileSync(channelFilePath,"utf-8").trim()==="canary")return"canary"}}catch{}return"stable"}export function writeChannel(channelFilePath,ch){try{writeFileSync(channelFilePath,ch,"utf-8")}catch{}}export function readSyncedVersion(versionFilePath){try{if(existsSync(versionFilePath)){const content=readFileSync(versionFilePath,"utf-8").trim(),[ch,sha]=content.split(/\s+/);if((ch==="stable"||ch==="canary")&&sha&&/^[0-9a-f]{40}$/i.test(sha))return{channel:ch,sha:sha.toLowerCase()}}}catch{}return null}export function writeSyncedVersion(versionFilePath,ch,sha){try{writeFileSync(versionFilePath,`${ch} ${sha}`,"utf-8")}catch{}}export function shouldCheckDirtyManagedPaths(args){return!args.force&&!args.alreadyRestarted}export function shouldShortCircuit(args){const{force,targetVersion,remoteSha,synced,channel}=args;if(force||targetVersion)return!1;if(!remoteSha||!synced)return!1;return synced.channel===channel&&synced.sha===remoteSha.toLowerCase()}export function resolveUpgradeMessage(ctx,currentChannel,isStableFlag){if(ctx.targetVersion)return`Installing Stacks v${ctx.targetVersion.replace(/^v/,"")}...`;if(ctx.channel==="canary")return"Upgrading to the latest canary build...";if(isStableFlag&¤tChannel==="canary")return"Switching back to the latest stable release...";return"Upgrading to the latest version..."}export function resolveSuccessMessage(ctx,currentVersion,newVersion,currentChannel,force){if(ctx.targetVersion){const pinned=ctx.targetVersion.replace(/^v/,"");if(currentVersion===pinned&&!force)return`Stacks is already at v${pinned}`;if(currentVersion)return`Installed Stacks v${pinned} (was v${currentVersion})`;return`Installed Stacks v${pinned}`}if(currentVersion&&newVersion&¤tVersion===newVersion&&!force){if(ctx.channel==="canary")return`Stacks is already up to date (canary ${currentVersion})`;return`Stacks is already up to date (v${currentVersion})`}if(currentVersion&&newVersion){const from=currentChannel==="canary"?`canary ${currentVersion}`:`v${currentVersion}`,to=ctx.channel==="canary"?`canary ${newVersion}`:`v${newVersion}`;return`Upgraded Stacks from ${from} to ${to}`}if(newVersion)return`Upgraded Stacks to ${ctx.channel==="canary"?`canary ${newVersion}`:`v${newVersion}`}`;return`Upgraded Stacks to ${ctx.ref} (latest)`}export const MANAGED_PATHS=[{localPath:"storage/framework/core",subPath:"storage/framework/core",label:"core",skip:["node_modules","dist","bun.lock",".DS_Store"]},{localPath:"storage/framework/defaults",subPath:"storage/framework/defaults",label:"defaults",skip:["node_modules","dist",".DS_Store",".discovered-models.json"]},{localPath:"storage/framework/types",subPath:"storage/framework/types",label:"types",skip:["server-auto-imports.d.ts","browser-auto-imports.d.ts",".DS_Store"]},{localPath:"buddy",subPath:"buddy",label:"buddy",isFile:!0,executable:!0},{localPath:"bootstrap",subPath:"bootstrap",label:"bootstrap",isFile:!0,executable:!0}];export function detectLocalStacks(projectRoot){const candidates=[process.env.STACKS_LOCAL_PATH,resolve(projectRoot,"..","stacks"),resolve(projectRoot,"..","..","stacks"),resolve(homedir(),"Code","stacks"),resolve(homedir(),"code","stacks"),resolve(homedir(),"projects","stacks"),resolve(homedir(),"src","stacks")];for(const candidate of candidates){if(!candidate)continue;const marker=join(candidate,"storage","framework","core","buddy","package.json");try{if(existsSync(marker)&&resolve(candidate)!==resolve(projectRoot))return resolve(candidate)}catch{}}return null}export function shouldAutoDetectLocalStacks(options){return!options.from&&!options.version}export async function snapshotTree(root,skip=[]){const out=new Map;if(!existsSync(root))return out;const skipSet=new Set(skip),stack=[{abs:root,rel:""}],hashJobs=[];while(stack.length>0){const{abs,rel}=stack.pop();let entries;try{entries=readdirSync(abs)}catch{continue}for(const name of entries){if(skipSet.has(name))continue;const childAbs=join(abs,name),childRel=rel?`${rel}/${name}`:name;let info;try{info=statSync(childAbs)}catch{continue}if(info.isDirectory())stack.push({abs:childAbs,rel:childRel});else if(info.isFile()){const size=info.size;hashJobs.push((async()=>{const hash=await hashFile(childAbs);out.set(childRel,{size,hash})})())}}}await Promise.all(hashJobs);return out}export function diffSnapshots(before,after){let added=0,changed=0,unchanged=0;for(const[rel,info]of after){const prev=before.get(rel);if(!prev)added++;else if(prev.size!==info.size||prev.hash!==info.hash)changed++;else unchanged++}let removed=0;for(const rel of before.keys())if(!after.has(rel))removed++;return{added,changed,removed,unchanged}}export function diffSnapshotsDetailed(before,after){const addedFiles=[],changedFiles=[],removedFiles=[];let unchanged=0;for(const[rel,info]of after){const prev=before.get(rel);if(!prev)addedFiles.push(rel);else if(prev.size!==info.size||prev.hash!==info.hash)changedFiles.push(rel);else unchanged++}for(const rel of before.keys())if(!after.has(rel))removedFiles.push(rel);addedFiles.sort();changedFiles.sort();removedFiles.sort();return{added:addedFiles.length,changed:changedFiles.length,removed:removedFiles.length,unchanged,addedFiles,changedFiles,removedFiles}}async function hashFile(absPath){try{const buf=await Bun.file(absPath).arrayBuffer();return Bun.hash(buf)}catch{return 0n}}
|
|
@@ -1,12 +1,13 @@
|
|
|
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(`
|
|
1
|
+
import{chmodSync,copyFileSync,cpSync,existsSync,mkdirSync,mkdtempSync,readFileSync,rmSync,statSync,writeFileSync}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";import{describeDanglingRouteActions,findDanglingRouteActions,pruneRetiredScaffoldRoutes}from"./route-actions";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
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
|
-
`);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
|
|
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 pruned=pruneRetiredScaffoldRoutes(projectRoot);for(const route of pruned)console.log(`Removed ${route.file}:${route.line}, the scaffold route to ${route.action}: removed in ${route.removedIn}, ${route.reason}`);const dangling=findDanglingRouteActions(projectRoot);if(dangling.length>0){console.warn(`${dangling.length} route(s) name an action this version does not ship. Point them at an action that exists, or delete them:`);for(const line of describeDanglingRouteActions(dangling).split(`
|
|
6
|
+
`))console.warn(` ${line}`)}}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
7
|
DRY RUN - preview only. No files will be written, nothing will be installed, no migrations will run.
|
|
7
8
|
`);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(`
|
|
8
9
|
A real run would:`);console.log(" - sync the managed paths above into place");if(!ctx.targetVersion){console.log(` - write channel "${ctx.channel}" to .stacks-channel`);console.log(previewSha?` - record synced sha ${previewSha.slice(0,7)} in .stacks-version`:" - record the resolved commit sha in .stacks-version (unavailable in this preview)")}if(!(!options.noPostinstall&&options.postinstall!==!1))console.log(" - skip post-sync hooks (--no-postinstall)");else if(aggregate.added+aggregate.changed+aggregate.removed===0)console.log(" - skip post-sync hooks (nothing changed)");else{console.log(" - regenerate the auto-import manifest");if(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))}))console.log(" - run `bun install` to refresh dependencies");if(existsSync(join(projectRoot,"database","migrations")))console.log(" - run pending migrations")}if(sawRemoved>0)console.log(`
|
|
9
10
|
(-N counts files no longer shipped upstream; the sync leaves them in place.)`);console.log(`
|
|
10
11
|
\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
12
|
`).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 - 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)}}
|
|
13
|
+
`).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 kept=new Map;for(const name of managed.skip??[]){const file=join(localTarget,name);try{if(existsSync(file)&&statSync(file).isFile())kept.set(file,readFileSync(file))}catch{}}const template=buildTemplateString(ref,managed.subPath);await downloadTemplate(template,{dir:localTarget,force:!0,forceClean:!1,preferOffline:!force});for(const[file,contents]of kept)writeFileSync(file,contents)}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)}}
|
|
@@ -27,6 +27,33 @@ export declare function migratePackageProjectTsconfig(projectRoot: string, optio
|
|
|
27
27
|
* AGENTS.md is always present after an update.
|
|
28
28
|
*/
|
|
29
29
|
export declare function detectProjectAiProviders(projectRoot: string): DetectedAiProvider[];
|
|
30
|
+
/**
|
|
31
|
+
* The framework's type declarations, which an app needs and never regenerates.
|
|
32
|
+
*
|
|
33
|
+
* `storage/framework/types` is mostly hand-written declarations ("derived
|
|
34
|
+
* rather than generated": model events, env, gates, registries, ...). A new app
|
|
35
|
+
* gets them with the rest of the scaffold, and `buddy upgrade` used to deliver
|
|
36
|
+
* none of them, so every declaration added after an app was created was simply
|
|
37
|
+
* missing - `model-events.d.ts` among them, which is what makes
|
|
38
|
+
* `'user:created'` exist on `AppEvents` (trifitla, upgrading 0.72.57 -> 0.75.11,
|
|
39
|
+
* failed typecheck on its own `app/Events.ts`).
|
|
40
|
+
*
|
|
41
|
+
* Added and updated, never pruned: an app can still hold older generated
|
|
42
|
+
* declarations here that something imports. The two files the runtime writes
|
|
43
|
+
* for this app are left alone.
|
|
44
|
+
* @defaultValue `{ source: 'project/storage/framework/types', target: 'storage/framework/types' }`
|
|
45
|
+
*/
|
|
46
|
+
export declare const FRAMEWORK_TYPES_SUPPORT_DIRECTORY: {
|
|
47
|
+
/** @defaultValue 'project/storage/framework/types' */
|
|
48
|
+
source: string;
|
|
49
|
+
/** @defaultValue 'storage/framework/types' */
|
|
50
|
+
target: string
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* Declarations the runtime generates per app; syncing them would clobber that.
|
|
54
|
+
* @defaultValue `['server-auto-imports.d.ts', 'browser-auto-imports.d.ts']`
|
|
55
|
+
*/
|
|
56
|
+
export declare const GENERATED_FRAMEWORK_TYPES: string[];
|
|
30
57
|
export declare interface ProjectStructureChange {
|
|
31
58
|
path: string
|
|
32
59
|
action: 'add' | 'update' | 'remove'
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{chmodSync,existsSync,lstatSync,mkdirSync,readFileSync,readdirSync,rmSync,statSync,writeFileSync}from"node:fs";import{dirname,join,relative}from"node:path";import{DEFAULTS_SYNC_MARKER,defaultsPackagePath}from"@stacksjs/path";const DEFAULTS_PACKAGE_IGNORES=new Set(["README.md","build.ts","node_modules","package.json","project","tests"]),LOCAL_DEFAULT_IGNORES=new Set([".DS_Store",".discovered-models.json",DEFAULTS_SYNC_MARKER,"dist","node_modules"]),LEGACY_PACKAGE_PROJECT_FILES=["pantry.lock"],SUPPORT_FILES=[{source:"project/buddy",target:"buddy",executable:!0},{source:"project/bootstrap",target:"bootstrap",executable:!0},{source:"project/storage/framework/tsconfig.app.json",target:"storage/framework/tsconfig.app.json"},{source:"project/storage/framework/tsconfig.base.json",target:"storage/framework/tsconfig.base.json"},{source:"project/storage/framework/server/tsconfig.docker.json",target:"storage/framework/server/tsconfig.docker.json"}];function sameFile(left,right,shallow=!1){if(!existsSync(left)||!existsSync(right))return!1;const leftStat=statSync(left),rightStat=statSync(right);if(leftStat.size!==rightStat.size)return!1;if(shallow)return!0;return readFileSync(left).equals(readFileSync(right))}function copyFileIfChanged(source,target,projectRoot,changes,options,executable=!1){if(sameFile(source,target,options.shallow&&options.dryRun)){if(executable&&!options.dryRun)chmodSync(target,493);return}changes.push({path:relative(projectRoot,target),action:existsSync(target)?"update":"add"});if(options.dryRun)return;mkdirSync(dirname(target),{recursive:!0});writeFileSync(target,readFileSync(source));if(executable)chmodSync(target,493)}function syncDirectory(source,target,projectRoot,changes,options,isRoot=!1){if(!options.dryRun)mkdirSync(target,{recursive:!0});const sourceEntries=new Set;for(const entry of readdirSync(source)){if(isRoot&&DEFAULTS_PACKAGE_IGNORES.has(entry))continue;sourceEntries.add(entry);const sourcePath=join(source,entry),targetPath=join(target,entry);if(lstatSync(sourcePath).isDirectory()){syncDirectory(sourcePath,targetPath,projectRoot,changes,options);continue}copyFileIfChanged(sourcePath,targetPath,projectRoot,changes,options)}if(!existsSync(target))return;for(const entry of readdirSync(target)){if(LOCAL_DEFAULT_IGNORES.has(entry)||sourceEntries.has(entry))continue;const targetPath=join(target,entry);changes.push({path:relative(projectRoot,targetPath),action:"remove"});if(!options.dryRun)rmSync(targetPath,{recursive:!0,force:!0})}}export function syncPackageProjectFiles(projectRoot,defaultsPackageRoot,options={}){const changes=[],targetDefaults=join(projectRoot,"storage/framework/defaults");syncDirectory(defaultsPackageRoot,targetDefaults,projectRoot,changes,options,!0);for(const file of SUPPORT_FILES){const source=join(defaultsPackageRoot,file.source);if(!existsSync(source))continue;copyFileIfChanged(source,join(projectRoot,file.target),projectRoot,changes,options,file.executable)}for(const legacyFile of LEGACY_PACKAGE_PROJECT_FILES){const target=join(projectRoot,legacyFile);if(!existsSync(target))continue;changes.push({path:legacyFile,action:"remove"});if(!options.dryRun)rmSync(target,{force:!0})}if(!options.dryRun)stampDefaultsSync(targetDefaults,defaultsPackageRoot);return changes}function stampDefaultsSync(targetDefaults,defaultsPackageRoot){const version=manifestVersion(defaultsPackageRoot);if(!version)return;const marker={version,syncedAt:new Date().toISOString()};mkdirSync(targetDefaults,{recursive:!0});writeFileSync(join(targetDefaults,DEFAULTS_SYNC_MARKER),`${JSON.stringify(marker,null,2)}
|
|
1
|
+
import{chmodSync,existsSync,lstatSync,mkdirSync,readFileSync,readdirSync,rmSync,statSync,writeFileSync}from"node:fs";import{dirname,join,relative}from"node:path";import{DEFAULTS_SYNC_MARKER,defaultsPackagePath}from"@stacksjs/path";const DEFAULTS_PACKAGE_IGNORES=new Set(["README.md","build.ts","node_modules","package.json","project","tests"]),LOCAL_DEFAULT_IGNORES=new Set([".DS_Store",".discovered-models.json",DEFAULTS_SYNC_MARKER,"dist","node_modules"]),LEGACY_PACKAGE_PROJECT_FILES=["pantry.lock"],SUPPORT_FILES=[{source:"project/buddy",target:"buddy",executable:!0},{source:"project/bootstrap",target:"bootstrap",executable:!0},{source:"project/storage/framework/tsconfig.app.json",target:"storage/framework/tsconfig.app.json"},{source:"project/storage/framework/tsconfig.base.json",target:"storage/framework/tsconfig.base.json"},{source:"project/storage/framework/server/tsconfig.docker.json",target:"storage/framework/server/tsconfig.docker.json"}];export const FRAMEWORK_TYPES_SUPPORT_DIRECTORY={source:"project/storage/framework/types",target:"storage/framework/types"},GENERATED_FRAMEWORK_TYPES=["server-auto-imports.d.ts","browser-auto-imports.d.ts"];function copyDirectoryIfChanged(source,target,projectRoot,changes,options,skip){for(const entry of readdirSync(source)){if(skip.has(entry)||LOCAL_DEFAULT_IGNORES.has(entry))continue;const sourcePath=join(source,entry),targetPath=join(target,entry);if(lstatSync(sourcePath).isDirectory())copyDirectoryIfChanged(sourcePath,targetPath,projectRoot,changes,options,skip);else copyFileIfChanged(sourcePath,targetPath,projectRoot,changes,options)}}function sameFile(left,right,shallow=!1){if(!existsSync(left)||!existsSync(right))return!1;const leftStat=statSync(left),rightStat=statSync(right);if(leftStat.size!==rightStat.size)return!1;if(shallow)return!0;return readFileSync(left).equals(readFileSync(right))}function copyFileIfChanged(source,target,projectRoot,changes,options,executable=!1){if(sameFile(source,target,options.shallow&&options.dryRun)){if(executable&&!options.dryRun)chmodSync(target,493);return}changes.push({path:relative(projectRoot,target),action:existsSync(target)?"update":"add"});if(options.dryRun)return;mkdirSync(dirname(target),{recursive:!0});writeFileSync(target,readFileSync(source));if(executable)chmodSync(target,493)}function syncDirectory(source,target,projectRoot,changes,options,isRoot=!1){if(!options.dryRun)mkdirSync(target,{recursive:!0});const sourceEntries=new Set;for(const entry of readdirSync(source)){if(isRoot&&DEFAULTS_PACKAGE_IGNORES.has(entry))continue;sourceEntries.add(entry);const sourcePath=join(source,entry),targetPath=join(target,entry);if(lstatSync(sourcePath).isDirectory()){syncDirectory(sourcePath,targetPath,projectRoot,changes,options);continue}copyFileIfChanged(sourcePath,targetPath,projectRoot,changes,options)}if(!existsSync(target))return;for(const entry of readdirSync(target)){if(LOCAL_DEFAULT_IGNORES.has(entry)||sourceEntries.has(entry))continue;const targetPath=join(target,entry);changes.push({path:relative(projectRoot,targetPath),action:"remove"});if(!options.dryRun)rmSync(targetPath,{recursive:!0,force:!0})}}export function syncPackageProjectFiles(projectRoot,defaultsPackageRoot,options={}){const changes=[],targetDefaults=join(projectRoot,"storage/framework/defaults");syncDirectory(defaultsPackageRoot,targetDefaults,projectRoot,changes,options,!0);for(const file of SUPPORT_FILES){const source=join(defaultsPackageRoot,file.source);if(!existsSync(source))continue;copyFileIfChanged(source,join(projectRoot,file.target),projectRoot,changes,options,file.executable)}const typesSource=join(defaultsPackageRoot,FRAMEWORK_TYPES_SUPPORT_DIRECTORY.source);if(existsSync(typesSource))copyDirectoryIfChanged(typesSource,join(projectRoot,FRAMEWORK_TYPES_SUPPORT_DIRECTORY.target),projectRoot,changes,options,new Set(GENERATED_FRAMEWORK_TYPES));for(const legacyFile of LEGACY_PACKAGE_PROJECT_FILES){const target=join(projectRoot,legacyFile);if(!existsSync(target))continue;changes.push({path:legacyFile,action:"remove"});if(!options.dryRun)rmSync(target,{force:!0})}if(!options.dryRun)stampDefaultsSync(targetDefaults,defaultsPackageRoot);return changes}function stampDefaultsSync(targetDefaults,defaultsPackageRoot){const version=manifestVersion(defaultsPackageRoot);if(!version)return;const marker={version,syncedAt:new Date().toISOString()};mkdirSync(targetDefaults,{recursive:!0});writeFileSync(join(targetDefaults,DEFAULTS_SYNC_MARKER),`${JSON.stringify(marker,null,2)}
|
|
2
2
|
`)}function manifestVersion(packageRoot){try{const version=JSON.parse(readFileSync(join(packageRoot,"package.json"),"utf8"))?.version;return typeof version==="string"?version:null}catch{return null}}export function measureDefaultsDrift(projectRoot,options={}){const packageRoot=defaultsPackagePath(projectRoot);if(!existsSync(join(packageRoot,"package.json")))return null;return syncPackageProjectFiles(projectRoot,packageRoot,{dryRun:!0,shallow:options.shallow})}export function summarizeStructureChanges(changes){const added=changes.filter((change)=>change.action==="add").length,updated=changes.filter((change)=>change.action==="update").length,removed=changes.filter((change)=>change.action==="remove").length;return`+${added} ~${updated} -${removed}`}export function migratePackageProjectManifest(pkg){const changes=[],scripts=pkg.scripts??{},setScript=(name,value)=>{if(scripts[name]===value)return;scripts[name]=value;changes.push({path:`package.json#scripts.${name}`,action:"update"})};setScript("buddy","./buddy");setScript("typecheck","bun x --bun tsc --noEmit -p tsconfig.json --pretty false");setScript("typecheck:app","bun x --bun tsc --noEmit -p tsconfig.json --pretty false");setScript("types:check","bun x --bun tsc --noEmit -p tsconfig.json --pretty false");if(scripts["build:reset"]?.includes("storage/framework"))setScript("build:reset","rm -rf node_modules pantry.lock && bun install && ./buddy generate:types && ./buddy lint:fix");if(scripts["build:framework-types"]){delete scripts["build:framework-types"];changes.push({path:"package.json#scripts.build:framework-types",action:"remove"})}pkg.scripts=scripts;if(Array.isArray(pkg.workspaces)){const next=pkg.workspaces.filter((workspace)=>workspace!=="storage/framework/core"&&!workspace.startsWith("storage/framework/core/"));if(next.length!==pkg.workspaces.length){pkg.workspaces=next;changes.push({path:"package.json#workspaces",action:"update"})}}return changes}export function migratePackageProjectTsconfig(projectRoot,options={}){const path=join(projectRoot,"tsconfig.json");if(!existsSync(path))return[];const raw=readFileSync(path,"utf8"),next=raw.replace(/"extends"\s*:\s*"\.\/storage\/framework\/core\/tsconfig\.json"/,'"extends": "./storage/framework/tsconfig.app.json"');if(next===raw)return[];if(!options.dryRun)writeFileSync(path,next);return[{path:"tsconfig.json",action:"update"}]}export function detectProjectAiProviders(projectRoot){const providers=new Set(["codex"]);if(existsSync(join(projectRoot,"CLAUDE.md"))||existsSync(join(projectRoot,".claude")))providers.add("claude");if(existsSync(join(projectRoot,".cursor")))providers.add("cursor");if(existsSync(join(projectRoot,".github/copilot-instructions.md")))providers.add("copilot");if(existsSync(join(projectRoot,"GEMINI.md")))providers.add("gemini");return[...providers]}
|
package/dist/upgrade/packages.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{existsSync,readdirSync,readFileSync,statSync,writeFileSync}from"node:fs";import{join,relative}from"node:path";import process from"node:process";import{runCommand}from"@stacksjs/cli";import{installedDefaultsVersion}from"@stacksjs/path";import{detectProjectAiProviders,measureDefaultsDrift,migratePackageProjectManifest,migratePackageProjectTsconfig,summarizeStructureChanges,syncPackageProjectFiles}from"./package-project";export function hasStacksDependency(pkg){return Boolean(pkg.dependencies?.stacks||pkg.devDependencies?.stacks)}export function standalonePackageUpdateCommand(){return"bun update"}export function frameworkPackageUpdateCommand(){return"bun update stacks"}export function installedStacksVersion(projectRoot){try{const manifest=JSON.parse(readFileSync(join(projectRoot,"node_modules/stacks/package.json"),"utf-8"));return typeof manifest.version==="string"?manifest.version:null}catch{return null}}export function needsFrameworkInstall(projectRoot,target){return installedStacksVersion(projectRoot)!==target}async function updateStandalonePackage(projectRoot,options){console.log(`
|
|
1
|
+
import{existsSync,readdirSync,readFileSync,statSync,writeFileSync}from"node:fs";import{join,relative}from"node:path";import process from"node:process";import{runCommand}from"@stacksjs/cli";import{installedDefaultsVersion}from"@stacksjs/path";import{detectProjectAiProviders,measureDefaultsDrift,migratePackageProjectManifest,migratePackageProjectTsconfig,summarizeStructureChanges,syncPackageProjectFiles}from"./package-project";import{describeDanglingRouteActions,findDanglingRouteActions,pruneRetiredScaffoldRoutes}from"./route-actions";export function hasStacksDependency(pkg){return Boolean(pkg.dependencies?.stacks||pkg.devDependencies?.stacks)}export function standalonePackageUpdateCommand(){return"bun update"}export function frameworkPackageUpdateCommand(){return"bun update stacks"}export function installedStacksVersion(projectRoot){try{const manifest=JSON.parse(readFileSync(join(projectRoot,"node_modules/stacks/package.json"),"utf-8"));return typeof manifest.version==="string"?manifest.version:null}catch{return null}}export function needsFrameworkInstall(projectRoot,target){return installedStacksVersion(projectRoot)!==target}async function updateStandalonePackage(projectRoot,options){console.log(`
|
|
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.
|
|
@@ -6,18 +6,20 @@ import{existsSync,readdirSync,readFileSync,statSync,writeFileSync}from"node:fs";
|
|
|
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
|
|
9
|
-
`)}console.log(` --dry-run: no files were written and nothing was installed.
|
|
9
|
+
`)}reportRouteActions(projectRoot,!0);console.log(` --dry-run: no files were written and nothing was installed.
|
|
10
10
|
`);process.exit(0)}if(changes.length>0||projectManifestChanges.length>0){const trailing=raw.endsWith(`
|
|
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)}const dependenciesNeedInstall=needsFrameworkInstall(projectRoot,target);if(changes.length>0||manifestChanges.length>0||projectManifestChanges.length>0||dependenciesNeedInstall||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 - 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}`)}if(structureChanges.length>0)await regenerateAutoImports(projectRoot);for(const provider of detectProjectAiProviders(projectRoot))if((await runCommand(`./buddy setup:ai ${provider} --force`,{cwd:projectRoot})).isErr){console.error(`
|
|
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),prunedRoutes=reportRouteActions(projectRoot,!1);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}`)}if(structureChanges.length>0)await regenerateAutoImports(projectRoot);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
|
-
`);process.exit(1)}if(!(changes.length>0||manifestChanges.length>0||projectManifestChanges.length>0||dependenciesNeedInstall||structureChanges.length>0||tsconfigChanges.length>0)&&!options.force)console.log(`
|
|
16
|
+
`);process.exit(1)}if(!(changes.length>0||manifestChanges.length>0||projectManifestChanges.length>0||dependenciesNeedInstall||structureChanges.length>0||tsconfigChanges.length>0||prunedRoutes>0)&&!options.force)console.log(`
|
|
17
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
|
-
`);process.exit(0)}
|
|
20
|
+
`);process.exit(0)}function reportRouteActions(projectRoot,dryRun){const pruned=pruneRetiredScaffoldRoutes(projectRoot,{dryRun});for(const route of pruned)console.log(` ${dryRun?"Would remove":"Removed"} ${route.file}:${route.line}, the scaffold route to ${route.action}: removed in ${route.removedIn}, ${route.reason}`);const dangling=findDanglingRouteActions(projectRoot);if(dangling.length>0){console.warn(`
|
|
21
|
+
${dangling.length} route${dangling.length===1?"":"s"} name${dangling.length===1?"s":""} an action this version does not ship. Point ${dangling.length===1?"it":"them"} at an action that exists, or delete ${dangling.length===1?"it":"them"}:`);for(const line of describeDanglingRouteActions(dangling).split(`
|
|
22
|
+
`))console.warn(` ${line}`);console.warn("")}return pruned.length}async function regenerateAutoImports(projectRoot){const proc=Bun.spawn({cmd:[process.execPath,"-e","await (await import('@stacksjs/server')).generateAutoImportFiles()"],cwd:projectRoot,stdout:"ignore",stderr:"pipe"});if(await proc.exited!==0){const stderr=(await new Response(proc.stderr).text()).trim();console.warn(` auto-imports were not regenerated (non-fatal): ${stderr.split(`
|
|
21
23
|
`).pop()||`exit ${proc.exitCode}`}`);return}console.log(" Regenerated auto-imports")}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(`
|
|
22
24
|
`)?`
|
|
23
25
|
`:"";writeFileSync(full,`${JSON.stringify(pkg,null,2)}${suffix}`)}}};walk(root);return changes}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where an `'Actions/…'` handler can live: the app first, then the framework
|
|
3
|
+
* defaults, vendored or installed. The same order the router resolves in.
|
|
4
|
+
*/
|
|
5
|
+
export declare function actionRoots(projectRoot: string): string[];
|
|
6
|
+
export declare function actionResolves(projectRoot: string, action: string): boolean;
|
|
7
|
+
/**
|
|
8
|
+
* Every `'Actions/…'` string in the app's route files that resolves to no file.
|
|
9
|
+
*
|
|
10
|
+
* Literal handler strings only: a resource route composes its five names at
|
|
11
|
+
* runtime, and a `Controller@method` handler is not an action path.
|
|
12
|
+
*/
|
|
13
|
+
export declare function findDanglingRouteActions(projectRoot: string): RouteActionReference[];
|
|
14
|
+
/**
|
|
15
|
+
* Remove the retired scaffold routes an app still carries, where it carries
|
|
16
|
+
* them unchanged and the action is gone. Returns what was (or, with `dryRun`,
|
|
17
|
+
* would be) removed.
|
|
18
|
+
*/
|
|
19
|
+
export declare function pruneRetiredScaffoldRoutes(projectRoot: string, options?: { dryRun?: boolean }): PrunedScaffoldRoute[];
|
|
20
|
+
/** One line per reference, for the upgrade summary and `buddy doctor`. */
|
|
21
|
+
export declare function describeDanglingRouteActions(references: RouteActionReference[]): string;
|
|
22
|
+
export declare const RETIRED_SCAFFOLD_ROUTES: RetiredScaffoldRoute[];
|
|
23
|
+
export declare interface RetiredScaffoldRoute {
|
|
24
|
+
file: string
|
|
25
|
+
statement: string
|
|
26
|
+
action: string
|
|
27
|
+
removedIn: string
|
|
28
|
+
reason: string
|
|
29
|
+
}
|
|
30
|
+
export declare interface RouteActionReference {
|
|
31
|
+
file: string
|
|
32
|
+
line: number
|
|
33
|
+
action: string
|
|
34
|
+
}
|
|
35
|
+
export declare interface PrunedScaffoldRoute extends RouteActionReference {
|
|
36
|
+
removedIn: string
|
|
37
|
+
reason: string
|
|
38
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import{existsSync,readdirSync,readFileSync,statSync,writeFileSync}from"node:fs";import{join,relative}from"node:path";export const RETIRED_SCAFFOLD_ROUTES=[{file:"routes/buddy.ts",statement:"route.get('/commands', 'Actions/Buddy/CommandsAction')",action:"Actions/Buddy/CommandsAction",removedIn:"0.74.5",reason:"the hand-written commands endpoint was dropped for `buddy list --json` (stacksjs/stacks#2056)"}];export function actionRoots(projectRoot){return[join(projectRoot,"app"),join(projectRoot,"storage/framework/defaults/app"),join(projectRoot,"node_modules/@stacksjs/defaults/app")]}export function actionResolves(projectRoot,action){return actionRoots(projectRoot).some((root)=>[".ts",".js"].some((extension)=>existsSync(join(root,`${action}${extension}`))))}function routeFiles(dir){if(!existsSync(dir))return[];const files=[];for(const entry of readdirSync(dir)){const path=join(dir,entry);if(statSync(path).isDirectory())files.push(...routeFiles(path));else if(/\.[cm]?ts$/.test(entry)&&!entry.endsWith(".d.ts"))files.push(path)}return files.sort()}function withoutLineComment(line){let quote=null;for(let i=0;i<line.length;i++){const char=line[i];if(quote){if(char==="\\")i++;else if(char===quote)quote=null;continue}if(char==="'"||char==='"'||char==="`")quote=char;else if(char==="/"&&line[i+1]==="/")return line.slice(0,i)}return line}const normalize=(statement)=>statement.replace(/\s+/g,"").replace(/;$/,"");export function findDanglingRouteActions(projectRoot){const dangling=[];for(const path of routeFiles(join(projectRoot,"routes")))readFileSync(path,"utf8").split(`
|
|
2
|
+
`).forEach((line,index)=>{const code=withoutLineComment(line);if(/^\s*(?:\*|\/\*)/.test(code))return;for(const match of code.matchAll(/(['"`])(Actions\/[\w/.-]+)\1/g)){const action=match[2];if(!actionResolves(projectRoot,action))dangling.push({file:relative(projectRoot,path),line:index+1,action})}});return dangling}export function pruneRetiredScaffoldRoutes(projectRoot,options={}){const pruned=[];for(const retired of RETIRED_SCAFFOLD_ROUTES){const path=join(projectRoot,retired.file);if(!existsSync(path)||actionResolves(projectRoot,retired.action))continue;const lines=readFileSync(path,"utf8").split(`
|
|
3
|
+
`),kept=lines.filter((line,index)=>{if(normalize(withoutLineComment(line))!==normalize(retired.statement))return!0;pruned.push({file:retired.file,line:index+1,action:retired.action,removedIn:retired.removedIn,reason:retired.reason});return!1});if(kept.length!==lines.length&&!options.dryRun)writeFileSync(path,kept.join(`
|
|
4
|
+
`))}return pruned}export function describeDanglingRouteActions(references){return references.map((ref)=>`${ref.file}:${ref.line} names ${ref.action}, which no longer exists`).join(`
|
|
5
|
+
`)}
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@stacksjs/actions",
|
|
3
3
|
"type": "module",
|
|
4
4
|
"sideEffects": false,
|
|
5
|
-
"version": "0.75.
|
|
5
|
+
"version": "0.75.14",
|
|
6
6
|
"description": "The Stacks actions.",
|
|
7
7
|
"author": "Chris Breuer",
|
|
8
8
|
"contributors": [
|
|
@@ -60,53 +60,53 @@
|
|
|
60
60
|
"prepublishOnly": "bun run build"
|
|
61
61
|
},
|
|
62
62
|
"dependencies": {
|
|
63
|
-
"@stacksjs/action-runner": "0.75.
|
|
64
|
-
"@stacksjs/ai": "0.75.
|
|
65
|
-
"@stacksjs/analytics": "0.75.
|
|
66
|
-
"@stacksjs/api": "0.75.
|
|
67
|
-
"@stacksjs/auth": "0.75.
|
|
68
|
-
"@stacksjs/build": "0.75.
|
|
63
|
+
"@stacksjs/action-runner": "0.75.14",
|
|
64
|
+
"@stacksjs/ai": "0.75.14",
|
|
65
|
+
"@stacksjs/analytics": "0.75.14",
|
|
66
|
+
"@stacksjs/api": "0.75.14",
|
|
67
|
+
"@stacksjs/auth": "0.75.14",
|
|
68
|
+
"@stacksjs/build": "0.75.14",
|
|
69
69
|
"@stacksjs/bumpx": "^0.2.6",
|
|
70
70
|
"@stacksjs/bunpress": "^0.2.8",
|
|
71
|
-
"@stacksjs/cli": "0.75.
|
|
72
|
-
"@stacksjs/cloud": "0.75.
|
|
73
|
-
"@stacksjs/cms": "0.75.
|
|
74
|
-
"@stacksjs/components": "^0.2.
|
|
75
|
-
"@stacksjs/config": "0.75.
|
|
76
|
-
"@stacksjs/database": "0.75.
|
|
77
|
-
"@stacksjs/desktop-build": "0.75.
|
|
78
|
-
"@stacksjs/dns": "0.75.
|
|
79
|
-
"@stacksjs/enums": "0.75.
|
|
80
|
-
"@stacksjs/env": "0.75.
|
|
81
|
-
"@stacksjs/error-handling": "0.75.
|
|
71
|
+
"@stacksjs/cli": "0.75.14",
|
|
72
|
+
"@stacksjs/cloud": "0.75.14",
|
|
73
|
+
"@stacksjs/cms": "0.75.14",
|
|
74
|
+
"@stacksjs/components": "^0.2.320",
|
|
75
|
+
"@stacksjs/config": "0.75.14",
|
|
76
|
+
"@stacksjs/database": "0.75.14",
|
|
77
|
+
"@stacksjs/desktop-build": "0.75.14",
|
|
78
|
+
"@stacksjs/dns": "0.75.14",
|
|
79
|
+
"@stacksjs/enums": "0.75.14",
|
|
80
|
+
"@stacksjs/env": "0.75.14",
|
|
81
|
+
"@stacksjs/error-handling": "0.75.14",
|
|
82
82
|
"@stacksjs/gitit": "^0.2.5",
|
|
83
|
-
"@stacksjs/i18n": "0.75.
|
|
84
|
-
"@stacksjs/image": "0.75.
|
|
85
|
-
"@stacksjs/logging": "0.75.
|
|
83
|
+
"@stacksjs/i18n": "0.75.14",
|
|
84
|
+
"@stacksjs/image": "0.75.14",
|
|
85
|
+
"@stacksjs/logging": "0.75.14",
|
|
86
86
|
"@stacksjs/logsmith": "^0.2.3",
|
|
87
|
-
"@stacksjs/orm": "0.75.
|
|
88
|
-
"@stacksjs/path": "0.75.
|
|
89
|
-
"@stacksjs/payments": "0.75.
|
|
90
|
-
"@stacksjs/queue": "0.75.
|
|
91
|
-
"@stacksjs/registry": "0.75.
|
|
92
|
-
"@stacksjs/router": "0.75.
|
|
87
|
+
"@stacksjs/orm": "0.75.14",
|
|
88
|
+
"@stacksjs/path": "0.75.14",
|
|
89
|
+
"@stacksjs/payments": "0.75.14",
|
|
90
|
+
"@stacksjs/queue": "0.75.14",
|
|
91
|
+
"@stacksjs/registry": "0.75.14",
|
|
92
|
+
"@stacksjs/router": "0.75.14",
|
|
93
93
|
"@stacksjs/rpx": "^0.11.54",
|
|
94
|
-
"@stacksjs/scheduler": "0.75.
|
|
95
|
-
"@stacksjs/search-engine": "0.75.
|
|
96
|
-
"@stacksjs/security": "0.75.
|
|
97
|
-
"@stacksjs/server": "0.75.
|
|
98
|
-
"@stacksjs/sites": "0.75.
|
|
99
|
-
"@stacksjs/storage": "0.75.
|
|
100
|
-
"@stacksjs/strings": "0.75.
|
|
101
|
-
"@stacksjs/stx": "^0.2.
|
|
94
|
+
"@stacksjs/scheduler": "0.75.14",
|
|
95
|
+
"@stacksjs/search-engine": "0.75.14",
|
|
96
|
+
"@stacksjs/security": "0.75.14",
|
|
97
|
+
"@stacksjs/server": "0.75.14",
|
|
98
|
+
"@stacksjs/sites": "0.75.14",
|
|
99
|
+
"@stacksjs/storage": "0.75.14",
|
|
100
|
+
"@stacksjs/strings": "0.75.14",
|
|
101
|
+
"@stacksjs/stx": "^0.2.320",
|
|
102
102
|
"@stacksjs/tlsx": "^0.13.19",
|
|
103
103
|
"@stacksjs/ts-cloud": "^0.16.14",
|
|
104
104
|
"@stacksjs/ts-md": "^0.1.1",
|
|
105
|
-
"@stacksjs/tunnel": "0.75.
|
|
106
|
-
"@stacksjs/types": "0.75.
|
|
107
|
-
"@stacksjs/utils": "0.75.
|
|
108
|
-
"@stacksjs/whois": "0.75.
|
|
109
|
-
"bun-plugin-stx": "^0.2.
|
|
105
|
+
"@stacksjs/tunnel": "0.75.14",
|
|
106
|
+
"@stacksjs/types": "0.75.14",
|
|
107
|
+
"@stacksjs/utils": "0.75.14",
|
|
108
|
+
"@stacksjs/whois": "0.75.14",
|
|
109
|
+
"bun-plugin-stx": "^0.2.320"
|
|
110
110
|
},
|
|
111
111
|
"peerDependencies": {
|
|
112
112
|
"craft-native": ">=0.0.72",
|
|
@@ -121,7 +121,7 @@
|
|
|
121
121
|
}
|
|
122
122
|
},
|
|
123
123
|
"devDependencies": {
|
|
124
|
-
"@stacksjs/validation": "0.75.
|
|
124
|
+
"@stacksjs/validation": "0.75.14",
|
|
125
125
|
"better-dx": "^0.2.24"
|
|
126
126
|
}
|
|
127
127
|
}
|