@stacksjs/actions 0.72.56 → 0.72.60
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/action.d.ts +20 -10
- package/dist/action.js +1 -1
- package/dist/generate/action-types.d.ts +9 -1
- package/dist/generate/action-types.js +42 -3
- package/dist/generate/index.js +1 -1
- package/dist/helpers/utils.js +1 -1
- package/dist/scaffold-crud.js +1 -1
- package/package.json +21 -20
package/dist/action.d.ts
CHANGED
|
@@ -4,10 +4,13 @@ import type { ExtractParams, InferValidations, JobOptions, RequestInstance } fro
|
|
|
4
4
|
* ts-validation `Validator<T>` rule with an optional error message.
|
|
5
5
|
*
|
|
6
6
|
* Kept loose at the index-signature level so existing actions with
|
|
7
|
-
* untyped validation objects keep compiling.
|
|
8
|
-
* body
|
|
9
|
-
*
|
|
10
|
-
* concrete rule types
|
|
7
|
+
* untyped validation objects keep compiling. Anything that declares
|
|
8
|
+
* `validations` gets the body inference (stacksjs/stacks#1851 Phase 2b) for
|
|
9
|
+
* free - this used to say `as const` was needed for
|
|
10
|
+
* {@link InferValidations} to read the concrete rule types, and it is not:
|
|
11
|
+
* a `Validator<T>` carries its own output type, so an ordinary object
|
|
12
|
+
* literal infers exactly the same. The note sent people adding an `as const`
|
|
13
|
+
* that changed nothing.
|
|
11
14
|
*
|
|
12
15
|
* Exported so the router can run validations without redeclaring the
|
|
13
16
|
* type (stacksjs/stacks#1870 R-3 — the previous copy in
|
|
@@ -33,10 +36,11 @@ export declare interface ActionResponse {
|
|
|
33
36
|
description: string
|
|
34
37
|
schema?: Record<string, unknown>
|
|
35
38
|
}
|
|
36
|
-
declare interface ActionOptions<TModel = string, TValidations extends ActionValidations | undefined = undefined, TPath extends string = '', /** * The payload `handle` receives when the action is invoked by something * other than the router - an event listener, for instance, which calls it * with the event object rather than a request (`app/Events.ts` maps * 'user:registered' straight onto an action name). Defaults to `never`, * which leaves every HTTP action typed exactly as before. */ TPayload = never,> {
|
|
39
|
+
declare interface ActionOptions<TModel = string, TValidations extends ActionValidations | undefined = undefined, TPath extends string = '', /** * The payload `handle` receives when the action is invoked by something * other than the router - an event listener, for instance, which calls it * with the event object rather than a request (`app/Events.ts` maps * 'user:registered' straight onto an action name). Defaults to `never`, * which leaves every HTTP action typed exactly as before. */ TPayload = never, /** * What `handle` answers with, inferred from the handler body. * * `handle` used to be declared as returning the whole {@link ActionResult} * union, which is a complete description of what the router will accept and * a useless one for anybody downstream: the concrete shape an action * actually returns was erased the moment it was written down. A typed client * asking "what comes back from this endpoint" got the union back and could * say nothing more. * * Inferred, so nothing has to be annotated, and constrained to * {@link ActionResult} so the router's contract is unchanged. Defaults to * the full union, which is what an action whose return type cannot be * inferred falls back to - the behaviour every action had before. */ TResult extends ActionResult = ActionResult, /** * How this action is invoked, which decides what `handle` is handed. * * `'http'` (the default) means the router calls it with a request. * `'event'` means something dispatches to it with a payload - the * `app/Events.ts` map does exactly that, calling `handle(payload, event)` * with the event object and no request anywhere near it. * * The distinction was already real and only expressible as the fourth * positional type argument (`new Action<string, undefined, '', Payload>`), * so almost nothing declared it - and an action wired to an event while * written against a request threw `request.get is not a function` the first * time its event fired. Saying `invocation: 'event'` types `handle`'s * parameter as the payload, inferred from `validations`. */ TInvocation extends 'http' | 'event' = 'http',> {
|
|
37
40
|
name?: string
|
|
38
41
|
description?: string
|
|
39
42
|
apiResponse?: boolean
|
|
43
|
+
invocation?: TInvocation
|
|
40
44
|
validations?: TValidations
|
|
41
45
|
method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE'
|
|
42
46
|
rate?: JobOptions['rate']
|
|
@@ -59,8 +63,12 @@ declare interface ActionOptions<TModel = string, TValidations extends ActionVali
|
|
|
59
63
|
) => void | Response | Promise<void | Response>
|
|
60
64
|
handle: {
|
|
61
65
|
bivarianceHack: (
|
|
62
|
-
request: [TPayload] extends [never]
|
|
63
|
-
|
|
66
|
+
request: [TPayload] extends [never]
|
|
67
|
+
? (TInvocation extends 'event'
|
|
68
|
+
? ResolveBody<TModel, TValidations>
|
|
69
|
+
: InferRequest<TModel, TValidations, TPath>)
|
|
70
|
+
: TPayload,
|
|
71
|
+
) => TResult | Promise<TResult>
|
|
64
72
|
}['bivarianceHack']
|
|
65
73
|
dependencies?: () => Record<string, unknown>
|
|
66
74
|
}
|
|
@@ -115,10 +123,11 @@ export type ActionResult = | Response
|
|
|
115
123
|
| boolean
|
|
116
124
|
| object
|
|
117
125
|
| unknown[];
|
|
118
|
-
export declare class Action<TModel = string, TValidations extends ActionValidations | undefined = undefined, TPath extends string = '', TPayload = never,> {
|
|
126
|
+
export declare class Action<TModel = string, TValidations extends ActionValidations | undefined = undefined, TPath extends string = '', TPayload = never, /** @see {@link ActionOptions} - inferred from `handle`'s return type. */ TResult extends ActionResult = ActionResult, /** @see {@link ActionOptions} - `'event'` types `handle` with the payload. */ TInvocation extends 'http' | 'event' = 'http',> {
|
|
119
127
|
name?: string;
|
|
120
128
|
description?: string;
|
|
121
129
|
apiResponse?: boolean;
|
|
130
|
+
invocation?: TInvocation;
|
|
122
131
|
rate?: ActionOptions['rate'];
|
|
123
132
|
tries?: ActionOptions['tries'];
|
|
124
133
|
backoff?: ActionOptions['backoff'];
|
|
@@ -134,13 +143,14 @@ export declare class Action<TModel = string, TValidations extends ActionValidati
|
|
|
134
143
|
csrf?: boolean;
|
|
135
144
|
authorize?: ActionOptions<TModel, TValidations, TPath>['authorize'];
|
|
136
145
|
before?: ActionOptions<TModel, TValidations, TPath>['before'];
|
|
137
|
-
handle: ActionOptions<TModel, TValidations, TPath, TPayload>['handle'];
|
|
146
|
+
handle: ActionOptions<TModel, TValidations, TPath, TPayload, TResult, TInvocation>['handle'];
|
|
138
147
|
model?: string;
|
|
139
148
|
modelDefinition?: TModel;
|
|
140
149
|
constructor({
|
|
141
150
|
name,
|
|
142
151
|
description,
|
|
143
152
|
apiResponse,
|
|
153
|
+
invocation,
|
|
144
154
|
validations,
|
|
145
155
|
handle,
|
|
146
156
|
rate,
|
|
@@ -159,7 +169,7 @@ export declare class Action<TModel = string, TValidations extends ActionValidati
|
|
|
159
169
|
authorize,
|
|
160
170
|
before,
|
|
161
171
|
dependencies,
|
|
162
|
-
}: ActionOptions<TModel, TValidations, TPath, TPayload>);
|
|
172
|
+
}: ActionOptions<TModel, TValidations, TPath, TPayload, TResult, TInvocation>);
|
|
163
173
|
get deps(): Record<string, unknown>;
|
|
164
174
|
overrideDependencies(overrides: Record<string, unknown>): void;
|
|
165
175
|
resetDependencies(): void;
|
package/dist/action.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export class Action{name;description;apiResponse;rate;tries;backoff;enabled;path;method;validations;requestFile;responses;responseHeaders;requestHeaders;skipCsrf;csrf;authorize;before;handle;model;modelDefinition;_dependenciesFactory;_resolvedDeps;_depsOverrides;constructor({name,description,apiResponse,validations,handle,rate,tries,backoff,enabled,path,method,requestFile,responses,responseHeaders,requestHeaders,model,skipCsrf,csrf,authorize,before,dependencies}){this.name=name;this.description=description;this.apiResponse=apiResponse;this.validations=validations;this.rate=rate;this.tries=tries;this.backoff=backoff;this.enabled=enabled;this.path=path;this.method=method;this.handle=handle;this.requestFile=requestFile;this.responses=responses;this.responseHeaders=responseHeaders;this.requestHeaders=requestHeaders;this.skipCsrf=skipCsrf;this.csrf=csrf;this.authorize=authorize;this.before=before;this._dependenciesFactory=dependencies;this.modelDefinition=model;if(model&&typeof model==="object"&&"name"in model)this.model=model.name;else if(typeof model==="string")this.model=model}get deps(){if(!this._resolvedDeps)this._resolvedDeps=this._dependenciesFactory?.()??{};if(this._depsOverrides)return{...this._resolvedDeps,...this._depsOverrides};return this._resolvedDeps}overrideDependencies(overrides){this._depsOverrides={...this._depsOverrides??{},...overrides}}resetDependencies(){this._depsOverrides=void 0;this._resolvedDeps=void 0}}
|
|
1
|
+
export class Action{name;description;apiResponse;invocation;rate;tries;backoff;enabled;path;method;validations;requestFile;responses;responseHeaders;requestHeaders;skipCsrf;csrf;authorize;before;handle;model;modelDefinition;_dependenciesFactory;_resolvedDeps;_depsOverrides;constructor({name,description,apiResponse,invocation,validations,handle,rate,tries,backoff,enabled,path,method,requestFile,responses,responseHeaders,requestHeaders,model,skipCsrf,csrf,authorize,before,dependencies}){this.name=name;this.invocation=invocation;this.description=description;this.apiResponse=apiResponse;this.validations=validations;this.rate=rate;this.tries=tries;this.backoff=backoff;this.enabled=enabled;this.path=path;this.method=method;this.handle=handle;this.requestFile=requestFile;this.responses=responses;this.responseHeaders=responseHeaders;this.requestHeaders=requestHeaders;this.skipCsrf=skipCsrf;this.csrf=csrf;this.authorize=authorize;this.before=before;this._dependenciesFactory=dependencies;this.modelDefinition=model;if(model&&typeof model==="object"&&"name"in model)this.model=model.name;else if(typeof model==="string")this.model=model}get deps(){if(!this._resolvedDeps)this._resolvedDeps=this._dependenciesFactory?.()??{};if(this._depsOverrides)return{...this._resolvedDeps,...this._depsOverrides};return this._resolvedDeps}overrideDependencies(overrides){this._depsOverrides={...this._depsOverrides??{},...overrides}}resetDependencies(){this._depsOverrides=void 0;this._resolvedDeps=void 0}}
|
|
@@ -1 +1,9 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Write `storage/framework/types/actions.d.ts`.
|
|
3
|
+
*
|
|
4
|
+
* Controllers stay a pattern rather than a union: they are addressed as
|
|
5
|
+
* `'Controllers/UserController@show'`, and the method half is a member name
|
|
6
|
+
* that would need the file parsed rather than listed. Catching the shape is
|
|
7
|
+
* what was already on offer for them, and it is unchanged.
|
|
8
|
+
*/
|
|
9
|
+
export declare function generateActionTypes(): Promise<void>;
|
|
@@ -1,7 +1,46 @@
|
|
|
1
|
-
import{readdir}from"node:fs/promises";import{log}from"@stacksjs/logging";import{path as p}from"@stacksjs/path";import{storage}from"@stacksjs/storage";
|
|
1
|
+
import{existsSync}from"node:fs";import{readdir}from"node:fs/promises";import{log}from"@stacksjs/logging";import{path as p}from"@stacksjs/path";import{storage}from"@stacksjs/storage";function actionDirectories(){const candidates=[p.appPath("Actions"),p.storagePath("framework/defaults/app/Actions")];try{const pkgJson=Bun.resolveSync("@stacksjs/defaults/package.json",process.cwd());candidates.push(`${pkgJson.slice(0,pkgJson.lastIndexOf("/"))}/app/Actions`)}catch{}return candidates.filter((dir)=>existsSync(dir))}async function collectActionPaths(){const found=new Set;for(const dir of actionDirectories()){const files=await readdir(dir,{recursive:!0});for(const file of files){if(!file.endsWith(".ts")||file.endsWith(".d.ts"))continue;found.add(`Actions/${file.slice(0,-3)}`)}}return[...found].sort()}async function collectMiddlewareAliases(){const found=new Set,candidates=[p.appPath("Middleware.ts"),p.storagePath("framework/defaults/app/Middleware.ts")];for(const file of candidates){if(!existsSync(file))continue;try{const module=await import(`${file}?t=${Date.now()}`);for(const alias of Object.keys(module.default??{}))found.add(alias)}catch{}}return[...found].sort()}async function collectNamedRoutes(){const registryFile=p.appPath("Routes.ts");if(!existsSync(registryFile))return{};try{const{listNamedRoutes,loadRoutes}=await import("@stacksjs/router"),{default:routeRegistry}=await import(registryFile);await loadRoutes(routeRegistry);return listNamedRoutes()}catch(error){log.debug("[generate:types] Could not load routes for named-route types",{error});return{}}}export async function generateActionTypes(){log.info("Generating action types...");const actions=await collectActionPaths();if(actions.length===0){log.debug("[generate:types] No action directories found; leaving actions.d.ts alone");return}const aliases=await collectMiddlewareAliases(),namedRoutes=await collectNamedRoutes(),union=actions.map((action)=>`
|
|
2
|
+
| '${action}'`).join(""),middlewareUnion=aliases.flatMap((alias)=>[`
|
|
3
|
+
| '${alias}'`,`
|
|
4
|
+
| '!${alias}'`]).join(""),routeEntries=Object.entries(namedRoutes).sort(([left],[right])=>left.localeCompare(right)).map(([name,routePath])=>`
|
|
5
|
+
'${name}': '${routePath}'`).join(""),contents=`// This file is auto-generated by Stacks. Do not edit this file manually.
|
|
2
6
|
// If you wish to rename an action, please do so by editing the file name.
|
|
3
7
|
//
|
|
4
8
|
// For more information, please visit: https://stacksjs.com/docs
|
|
5
9
|
|
|
6
|
-
|
|
7
|
-
|
|
10
|
+
/**
|
|
11
|
+
* Every action this application can route to: its own, plus the framework
|
|
12
|
+
* defaults it has not overridden.
|
|
13
|
+
*/
|
|
14
|
+
export type ActionPath =${union}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Every middleware alias this application registers, and its negated form.
|
|
18
|
+
*/
|
|
19
|
+
export type MiddlewareAlias =${middlewareUnion||`
|
|
20
|
+
| string`}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Every named route, and the path it resolves to.
|
|
24
|
+
*
|
|
25
|
+
* A type alias, NOT an interface: only aliases of object literals get an
|
|
26
|
+
* implicit index signature, so an interface here fails the router's
|
|
27
|
+
* \`extends Record<string, string>\` shape check and the whole map is quietly
|
|
28
|
+
* ignored.
|
|
29
|
+
*/
|
|
30
|
+
export type NamedRoutes = {${routeEntries?`${routeEntries}
|
|
31
|
+
`:""}}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Handed to the router, so \`route.get(path, 'Actions/\u2026')\` and
|
|
35
|
+
* \`.middleware('auth')\` are checked against the lists above rather than
|
|
36
|
+
* against a string. A controller reference stays a pattern - the method half is
|
|
37
|
+
* a member name, not a filename.
|
|
38
|
+
*/
|
|
39
|
+
declare module '@stacksjs/bun-router' {
|
|
40
|
+
interface RouterTypeRegistry {
|
|
41
|
+
actions: ActionPath | \`\${string}Controller@\${string}\`
|
|
42
|
+
middleware: MiddlewareAlias
|
|
43
|
+
routes: NamedRoutes
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
`;await storage.writeFile(p.frameworkPath("types/actions.d.ts"),contents);log.debug(`[generate:types] Wrote ${actions.length} action paths, ${aliases.length} middleware aliases and ${Object.keys(namedRoutes).length} named routes`)}
|
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 - 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")}
|
|
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{generateActionTypes}from"./action-types";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();await generateActionTypes();try{const{generateServerAutoImportTypes}=await import("@stacksjs/server");await generateServerAutoImportTypes()}catch(error){log.debug("[generate:types] Could not refresh server auto-import declarations",{error})}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/helpers/utils.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var {require}=import.meta;import{existsSync}from"node:fs";import{
|
|
1
|
+
var {require}=import.meta;import{existsSync}from"node:fs";import{delimiter,join}from"node:path";import process from"node:process";import{buddyOptions,runCommand}from"@stacksjs/cli";import{err}from"@stacksjs/error-handling";import{log}from"@stacksjs/logging";import*as p from"@stacksjs/path";export function publishedActionCandidates(action,packageRoot){let root=packageRoot;if(!root)try{const pkgUrl=import.meta.resolve("@stacksjs/actions/package.json");if(pkgUrl){const pkgPath=new URL(pkgUrl).pathname;root=pkgPath.slice(0,pkgPath.lastIndexOf("/"))}}catch{return[]}if(!root)return[];return[`${root}/dist/${action}.js`,`${root}/dist/src/${action}.js`,`${root}/src/${action}.ts`]}export function developmentConditionForProject(projectRoot){return existsSync(join(projectRoot,"storage/framework/core"))&&existsSync(join(projectRoot,"node_modules/@stacksjs/env/src/index.ts"))?"--conditions development":""}export function actionNodePath(projectRoot,existingNodePath){const nodeModulesPath=join(projectRoot,"node_modules"),pantryPath=join(projectRoot,"pantry"),paths=[nodeModulesPath];for(const entry of existingNodePath?.split(delimiter)??[])if(entry&&entry!==nodeModulesPath&&entry!==pantryPath&&!paths.includes(entry))paths.push(entry);paths.push(pantryPath);return paths.join(delimiter)}async function resolveActionFile(action,projectRoot){const candidates=[];if(projectRoot)candidates.push(join(projectRoot,`storage/framework/core/actions/src/${action}.ts`));candidates.push(p.actionsPath(`src/${action}.ts`));candidates.push(...publishedActionCandidates(action));for(const candidate of candidates)if(await Bun.file(candidate).exists())return candidate;return null}export async function runAction(action,options){log.debug(`[action] Running: ${action}`);if(action==="dev/views")try{const nodePath=actionNodePath(p.projectPath(),process.env.NODE_PATH);if(process.env.NODE_PATH!==nodePath){process.env.NODE_PATH=nodePath;require("module").Module._initPaths?.()}const viewsEntries=[p.projectPath("storage/framework/core/actions/src/dev/views.ts"),p.frameworkPath("actions/src/dev/views.ts"),...publishedActionCandidates("dev/views")];for(const entry of viewsEntries)if(existsSync(entry)){await import(entry);return{ok:!0,value:{}}}return err("dev/views entry not found")}catch(error){return err(`Failed to start dev server: ${error}`)}const isLikelyCoreAction=action.includes("/")||["dev","build","install","upgrade","migrate"].some((prefix)=>action.startsWith(prefix)),userActionsBase=options?.cwd?join(String(options.cwd),"app/Actions"):p.userActionsPath();if(!isLikelyCoreAction&&existsSync(userActionsBase)){const glob=new Bun.Glob("**/*.{ts,js}"),scanOptions={cwd:userActionsBase,onlyFiles:!0,absolute:!0},matchingFiles=[],basePath=userActionsBase;for await(const file of glob.scan(scanOptions)){if(file.replace(`${basePath}/`,"").replace(/\.(ts|js)$/,"")===action||file.endsWith(`${action}.ts`)||file.endsWith(`${action}.js`)){log.debug(`[action] Resolved: ${action} \u2192 ${file}`);return await(await import(file)).default.handle(void 0)}matchingFiles.push(file)}for(const file of matchingFiles)try{const a=await import(file);if(a.name===action){log.debug(`[action] Resolved: ${action} \u2192 ${file}`);return await a.handle()}}catch(error){}}const path=await resolveActionFile(action,options?.cwd?String(options.cwd):void 0);if(!path)return err(`Action '${action}' not found in storage/framework/core/actions/src or @stacksjs/actions`);log.debug(`[action] Resolved: ${action} \u2192 ${path}`);const isDevAction=action.startsWith("dev/"),watchFlag=developmentWatchFlag(action),developmentCondition=developmentConditionForProject(p.projectPath()),opts=isDevAction?"":buddyOptions(options)||"",cmd=["bun",developmentCondition,watchFlag,path,opts].filter(Boolean).join(" "),nodePath=actionNodePath(p.projectPath(),process.env.NODE_PATH),shouldInherit=options?.verbose||isDevAction&&!options?.quiet,optionsWithCwd={cwd:options?.cwd||p.projectPath(),...options,stdout:shouldInherit?"inherit":void 0,stderr:shouldInherit?"inherit":void 0,env:{...options?.env,NODE_PATH:nodePath,...isDevAction?{STACKS_DEV_SERVER:"1"}:{}}},result=await runCommand(cmd,optionsWithCwd);log.debug(`[action] Completed: ${action}`);return result}export function developmentWatchFlag(action){if(!action.startsWith("dev/"))return"";return action==="dev/dashboard"||action==="dev/desktop"?"":"--watch"}export async function runActions(actions,options){if(!actions.length)return err("No actions were specified");for(const action of actions)if(!hasAction(action))return err(`The specified action "${action}" does not exist`);return await runActionSequence(actions,options)}export async function runActionSequence(actions,options,runner=runAction){let result;for(const action of actions){result=await runner(action,options);if(result?.isErr)return result}return result}export function hasAction(action){const userActionPatterns=[`${action}.ts`,`${action}`,`Dashboard/${action}.ts`,`Dashboard/${action}`,`Buddy/${action}.ts`,`Buddy/${action}`],actionPatterns=[`src/${action}.ts`,`src/${action}`,`${action}.ts`,`${action}`];return[...userActionPatterns.map((pattern)=>p.userActionsPath(pattern)),...actionPatterns.map((pattern)=>p.actionsPath(pattern)),...publishedActionCandidates(action)].some((candidate)=>existsSync(candidate))}
|
package/dist/scaffold-crud.js
CHANGED
|
@@ -83,4 +83,4 @@ ${migrationColumns(fields)}
|
|
|
83
83
|
`);log.info(`[dry-run] would write ${path} (${lines.length} lines)`);process.stdout.write(`${lines.slice(0,60).map((l)=>` ${l}`).join(`
|
|
84
84
|
`)}
|
|
85
85
|
|
|
86
|
-
`);return}await writeTextFile({path,data:contents})}export async function scaffoldCrud(name,options){const Model=pascalCase(name);if(!Model)throw Error("scaffold:crud requires a non-empty resource name");const fields=parseFields(options.fields);log.info(`Scaffolding CRUD for ${italic(Model)} (${fields.length||"no"} fields)`);await writeFile(p.userModelsPath(`${Model}.ts`),modelStub(Model,fields));const migrationName=`${Date.now().toString()}-create-${pluralize(snakeCase(name))}-table.ts`;await writeFile(p.userMigrationsPath(migrationName),migrationStub(name,fields));for(const kind of["Index","Show","Store","Update","Destroy"])await writeFile(p.userActionsPath(`${Model}${kind}Action.ts`),actionStub(name,kind,fields));log.success(`CRUD scaffold complete: model + migration + 5 actions for ${Model}`);log.info(`Next: register \`route.resource('${pluralize(snakeCase(name))}', '${Model}')\` in routes/api.ts`)}
|
|
86
|
+
`);return}await writeTextFile({path,data:contents})}export async function scaffoldCrud(name,options){const Model=pascalCase(name);if(!Model)throw Error("scaffold:crud requires a non-empty resource name");const fields=parseFields(options.fields);log.info(`Scaffolding CRUD for ${italic(Model)} (${fields.length||"no"} fields)`);await writeFile(p.userModelsPath(`${Model}.ts`),modelStub(Model,fields));const migrationName=`${Date.now().toString()}-create-${pluralize(snakeCase(name))}-table.ts`;await writeFile(p.userMigrationsPath(migrationName),migrationStub(name,fields));for(const kind of["Index","Show","Store","Update","Destroy"])await writeFile(p.userActionsPath(`${Model}${kind}Action.ts`),actionStub(name,kind,fields));log.success(`CRUD scaffold complete: model + migration + 5 actions for ${Model}`);log.info(`Next: run \`buddy generate:types\`, then register \`route.resource('${pluralize(snakeCase(name))}', '${Model}')\` in routes/api.ts`)}
|
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.60",
|
|
6
6
|
"description": "The Stacks actions.",
|
|
7
7
|
"author": "Chris Breuer",
|
|
8
8
|
"contributors": [
|
|
@@ -56,38 +56,39 @@
|
|
|
56
56
|
"scripts": {
|
|
57
57
|
"build": "bun build.ts",
|
|
58
58
|
"typecheck": "bun tsc --noEmit",
|
|
59
|
+
"typecheck:types": "bun tsc --noEmit -p tsconfig.type-tests.json --pretty false",
|
|
59
60
|
"prepublishOnly": "bun run build"
|
|
60
61
|
},
|
|
61
62
|
"dependencies": {
|
|
62
|
-
"@stacksjs/config": "0.72.
|
|
63
|
+
"@stacksjs/config": "0.72.60",
|
|
63
64
|
"@stacksjs/bumpx": "^0.2.6",
|
|
64
65
|
"@stacksjs/bunpress": "^0.2.8",
|
|
65
66
|
"@stacksjs/logsmith": "^0.2.3",
|
|
66
|
-
"@stacksjs/registry": "0.72.
|
|
67
|
+
"@stacksjs/registry": "0.72.60",
|
|
67
68
|
"@stacksjs/stx": "^0.2.219",
|
|
68
69
|
"@stacksjs/ts-cloud": "^0.11.4",
|
|
69
70
|
"@stacksjs/ts-md": "^0.1.1"
|
|
70
71
|
},
|
|
71
72
|
"devDependencies": {
|
|
72
|
-
"@stacksjs/api": "0.72.
|
|
73
|
-
"@stacksjs/cli": "0.72.
|
|
74
|
-
"@stacksjs/database": "0.72.
|
|
73
|
+
"@stacksjs/api": "0.72.60",
|
|
74
|
+
"@stacksjs/cli": "0.72.60",
|
|
75
|
+
"@stacksjs/database": "0.72.60",
|
|
75
76
|
"@stacksjs/tlsx": "^0.13.2",
|
|
76
77
|
"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.
|
|
78
|
+
"@stacksjs/dns": "0.72.60",
|
|
79
|
+
"@stacksjs/enums": "0.72.60",
|
|
80
|
+
"@stacksjs/env": "0.72.60",
|
|
81
|
+
"@stacksjs/error-handling": "0.72.60",
|
|
82
|
+
"@stacksjs/image": "0.72.60",
|
|
83
|
+
"@stacksjs/logging": "0.72.60",
|
|
84
|
+
"@stacksjs/path": "0.72.60",
|
|
85
|
+
"@stacksjs/security": "0.72.60",
|
|
86
|
+
"@stacksjs/cms": "0.72.60",
|
|
87
|
+
"@stacksjs/sites": "0.72.60",
|
|
88
|
+
"@stacksjs/storage": "0.72.60",
|
|
89
|
+
"@stacksjs/strings": "0.72.60",
|
|
90
|
+
"@stacksjs/utils": "0.72.60",
|
|
91
|
+
"@stacksjs/validation": "0.72.60"
|
|
91
92
|
},
|
|
92
93
|
"peerDependencies": {
|
|
93
94
|
"pickier": "^0.1.58",
|