@stacksjs/actions 0.70.87 → 0.70.90
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.js +78 -2
- package/dist/actions.js +2 -2
- package/dist/add.js +20 -2
- package/dist/ai/request-model-access.js +10 -2
- package/dist/auth/client.js +44 -3
- package/dist/auth/prune.js +35 -2
- package/dist/auth/setup.js +152 -36
- package/dist/auth/token.js +10 -2
- package/dist/blog-admin.d.ts +55 -0
- package/dist/blog-admin.js +156 -0
- package/dist/blog.d.ts +11 -0
- package/dist/blog.js +306 -56
- package/dist/build/cli.js +6 -2
- package/dist/build/component-libs.js +7 -2
- package/dist/build/core.js +55 -2
- package/dist/build/desktop.js +5 -2
- package/dist/build/docs.js +5 -2
- package/dist/build/frontend-static.js +123 -8
- package/dist/build/server.js +6 -2
- package/dist/build/stacks.js +8 -2
- package/dist/build/views.js +11 -2
- package/dist/build.js +64 -2
- package/dist/bump.js +162 -7
- package/dist/cdn/invalidate-cache.js +0 -1
- package/dist/changelog.js +27 -3
- package/dist/check/ports.js +16 -2
- package/dist/clean.js +4 -2
- package/dist/commit.js +12 -2
- package/dist/copy-types.js +10 -3
- package/dist/database/seed.js +26 -2
- package/dist/deploy/create-receipt-rule.js +36 -2
- package/dist/deploy/index.js +1318 -44
- package/dist/dev/api.js +94 -6
- package/dist/dev/components.js +7 -2
- package/dist/dev/dashboard-globals.js +89 -3
- package/dist/dev/dashboard-utils.js +380 -2
- package/dist/dev/dashboard.js +435 -8
- package/dist/dev/desktop.js +5 -2
- package/dist/dev/docs.js +24 -2
- package/dist/dev/index.js +29 -2
- package/dist/dev/migrate-watcher.js +74 -2
- package/dist/dev/system-tray.js +5 -2
- package/dist/dev/views.js +147 -2
- package/dist/discover-packages.js +32 -2
- package/dist/domains/add.js +61 -2
- package/dist/domains/purchase.js +66 -2
- package/dist/domains/remove.js +24 -2
- package/dist/examples.js +34 -2
- package/dist/find-projects.js +16 -2
- package/dist/fresh.js +17 -2
- package/dist/generate/action-types.js +9 -4
- package/dist/generate/component-meta.js +9 -3
- package/dist/generate/custom-cli-file.js +20 -3
- package/dist/generate/env-files.js +46 -6
- package/dist/generate/ide-helpers.js +8 -3
- package/dist/generate/index.js +158 -2
- package/dist/generate/lib-entries.js +16 -3
- package/dist/generate/pantry-file.js +46 -2
- package/dist/generate/symlink-core.js +2 -2
- package/dist/generate/types.js +3 -2
- package/dist/generate/vscode-custom-data.js +9 -3
- package/dist/generate/web-types.js +0 -1
- package/dist/helpers/component-meta.js +295 -2
- package/dist/helpers/index.js +1 -2
- package/dist/helpers/lib-entries.js +73 -2
- package/dist/helpers/package-json.js +45 -14
- package/dist/helpers/utils.js +105 -2
- package/dist/helpers/vscode-custom-data.js +30 -2
- package/dist/index.d.ts +13 -0
- package/dist/index.js +50 -2
- package/dist/key-generate.js +24 -2
- package/dist/lint/fix.js +4 -3
- package/dist/lint/index.js +4 -3
- package/dist/lint/lint.js +34 -2
- package/dist/make-command.js +64 -14
- package/dist/make-job.js +41 -16
- package/dist/make-policy.js +74 -26
- package/dist/make-resource.js +50 -21
- package/dist/make.js +328 -2
- package/dist/migrate/database.js +16 -2
- package/dist/migrate/fresh.js +31 -2
- package/dist/orm/base.js +0 -2
- package/dist/orm/test.js +0 -1
- package/dist/prepublish.js +16 -2
- package/dist/queue/clear.js +42 -2
- package/dist/queue/dlq-purge.js +28 -2
- package/dist/queue/dlq-retry.js +35 -2
- package/dist/queue/dlq.js +55 -5
- package/dist/queue/failed.js +69 -5
- package/dist/queue/flush.js +49 -3
- package/dist/queue/inspect.js +154 -8
- package/dist/queue/list.js +69 -5
- package/dist/queue/monitor.js +94 -6
- package/dist/queue/pause.js +28 -2
- package/dist/queue/quarantine.js +42 -3
- package/dist/queue/resume.js +27 -2
- package/dist/queue/retry.js +41 -2
- package/dist/queue/schedule-list.js +33 -5
- package/dist/queue/schedule.js +12 -2
- package/dist/queue/status.js +68 -5
- package/dist/queue/table.js +10 -2
- package/dist/queue/unquarantine.js +27 -2
- package/dist/queue/work.js +57 -2
- package/dist/release.js +21 -3
- package/dist/route/list.js +78 -3
- package/dist/run-action.js +1 -2
- package/dist/saas/setup.js +9 -2
- package/dist/scaffold-crud.js +164 -40
- package/dist/schedule/run.js +7 -2
- package/dist/search/flush.js +10 -2
- package/dist/search/import.js +10 -2
- package/dist/search/settings-list.js +10 -2
- package/dist/search/settings.js +9 -2
- package/dist/serve/api.js +35 -2
- package/dist/setup/index.js +1 -2
- package/dist/setup/ssl.js +271 -2
- package/dist/stacks.js +265 -2
- package/dist/templates.js +301 -2
- package/dist/test/feature.js +11 -2
- package/dist/test/index.js +11 -2
- package/dist/test/ui.js +4 -2
- package/dist/test/unit.js +11 -2
- package/dist/typecheck.js +4 -2
- package/dist/types.js +16 -2
- package/dist/upgrade/binary.js +29 -2
- package/dist/upgrade/bun.js +0 -1
- package/dist/upgrade/dependencies.js +0 -2
- package/dist/upgrade/framework-utils.js +193 -2
- package/dist/upgrade/framework.js +310 -7
- package/dist/upgrade/index.js +17 -2
- package/dist/upgrade/packages.js +99 -12
- package/dist/upgrade/shell.js +40 -4
- package/dist/upgrade.js +38 -2
- package/package.json +16 -16
- package/dist/chunk-0c8sd1t6.js +0 -2
- package/dist/chunk-0g2nzhx9.js +0 -240
- package/dist/chunk-0gadhvn7.js +0 -3
- package/dist/chunk-0h27aa4r.js +0 -93
- package/dist/chunk-3peh8kqs.js +0 -851
- package/dist/chunk-62gdfav8.js +0 -3
- package/dist/chunk-6bk53y02.js +0 -3
- package/dist/chunk-78c5n3s2.js +0 -3
- package/dist/chunk-c74qv38e.js +0 -17
- package/dist/chunk-cdk2r2jd.js +0 -1
- package/dist/chunk-dbz9dp2h.js +0 -3
- package/dist/chunk-e142dheq.js +0 -3
- package/dist/chunk-ec4gky40.js +0 -3
- package/dist/chunk-h3d81wrg.js +0 -3
- package/dist/chunk-m3q0x4h0.js +0 -688
- package/dist/chunk-mqgbyhph.js +0 -5
- package/dist/chunk-n24m8prb.js +0 -3
- package/dist/chunk-n5eqwhck.js +0 -7
- package/dist/chunk-nsvnqv8y.js +0 -5
- package/dist/chunk-nty6mmx4.js +0 -283
- package/dist/chunk-s735m9mg.js +0 -1
- package/dist/chunk-tpb8jgsk.js +0 -7
- package/dist/chunk-tsbk5vne.js +0 -3
- package/dist/chunk-xg9xscqz.js +0 -1
- package/dist/chunk-y6e448he.js +0 -7
package/dist/make-policy.js
CHANGED
|
@@ -1,35 +1,61 @@
|
|
|
1
|
-
|
|
2
|
-
import
|
|
1
|
+
import { log } from "@stacksjs/logging";
|
|
2
|
+
import * as p from "@stacksjs/path";
|
|
3
|
+
import { doesExist, get, writeFile } from "@stacksjs/storage";
|
|
4
|
+
export async function makePolicy(options) {
|
|
5
|
+
const name = options.name;
|
|
6
|
+
if (!name) {
|
|
7
|
+
log.error("Policy name is required");
|
|
8
|
+
return !1;
|
|
9
|
+
}
|
|
10
|
+
const policyName = name.endsWith("Policy") ? name : `${name}Policy`, modelName = options.model || policyName.replace("Policy", ""), content = generatePolicyContent(policyName, modelName), filePath = p.appPath(`Policies/${policyName}.ts`);
|
|
11
|
+
try {
|
|
12
|
+
const policiesDir = p.appPath("Policies");
|
|
13
|
+
if (!doesExist(policiesDir)) {
|
|
14
|
+
const { mkdirSync } = await import("@stacksjs/storage");
|
|
15
|
+
mkdirSync(policiesDir, { recursive: !0 });
|
|
16
|
+
}
|
|
17
|
+
await writeFile(filePath, content);
|
|
18
|
+
log.success(`Created policy: ${filePath}`);
|
|
19
|
+
if (options.register !== !1)
|
|
20
|
+
await registerPolicy(modelName, policyName);
|
|
21
|
+
return !0;
|
|
22
|
+
} catch (error) {
|
|
23
|
+
log.error(`Failed to create policy: ${error.message}`);
|
|
24
|
+
return !1;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function generatePolicyContent(policyName, modelName) {
|
|
28
|
+
return `import type _User from '../models/User'
|
|
3
29
|
import { BasePolicy } from '@stacksjs/auth'
|
|
4
30
|
|
|
5
31
|
type UserModel = _User
|
|
6
32
|
|
|
7
33
|
/**
|
|
8
|
-
* ${
|
|
34
|
+
* ${policyName}
|
|
9
35
|
*
|
|
10
|
-
* This policy controls authorization for ${
|
|
36
|
+
* This policy controls authorization for ${modelName} model actions.
|
|
11
37
|
*
|
|
12
38
|
* Usage:
|
|
13
39
|
* 1. Register in app/Gates.ts:
|
|
14
|
-
* policies: { '${
|
|
40
|
+
* policies: { '${modelName}': '${policyName}' }
|
|
15
41
|
*
|
|
16
42
|
* 2. Use in your code:
|
|
17
43
|
* import { Gate } from '@stacksjs/auth'
|
|
18
|
-
* await Gate.authorize('view', user, ${
|
|
44
|
+
* await Gate.authorize('view', user, ${modelName.toLowerCase()})
|
|
19
45
|
*
|
|
20
46
|
* 3. Or use the middleware:
|
|
21
|
-
* route.put('/${
|
|
22
|
-
* .middleware('can:update,${
|
|
47
|
+
* route.put('/${modelName.toLowerCase()}s/:${modelName.toLowerCase()}', 'Update${modelName}')
|
|
48
|
+
* .middleware('can:update,${modelName.toLowerCase()}')
|
|
23
49
|
*/
|
|
24
50
|
|
|
25
|
-
// TODO: Import or define your ${
|
|
26
|
-
interface ${
|
|
51
|
+
// TODO: Import or define your ${modelName} type
|
|
52
|
+
interface ${modelName} {
|
|
27
53
|
id: number
|
|
28
54
|
user_id: number
|
|
29
55
|
// Add your model properties here
|
|
30
56
|
}
|
|
31
57
|
|
|
32
|
-
export default class ${
|
|
58
|
+
export default class ${policyName} extends BasePolicy<${modelName}> {
|
|
33
59
|
/**
|
|
34
60
|
* Run before any other checks.
|
|
35
61
|
* Return true to allow, false to deny, null to continue.
|
|
@@ -41,54 +67,76 @@ export default class ${q} extends BasePolicy<${b}> {
|
|
|
41
67
|
}
|
|
42
68
|
|
|
43
69
|
/**
|
|
44
|
-
* Determine if the user can view any ${
|
|
70
|
+
* Determine if the user can view any ${modelName.toLowerCase()}s.
|
|
45
71
|
*/
|
|
46
72
|
viewAny(user: UserModel | null): boolean {
|
|
47
73
|
return true
|
|
48
74
|
}
|
|
49
75
|
|
|
50
76
|
/**
|
|
51
|
-
* Determine if the user can view the ${
|
|
77
|
+
* Determine if the user can view the ${modelName.toLowerCase()}.
|
|
52
78
|
*/
|
|
53
|
-
view(user: UserModel | null, model: ${
|
|
79
|
+
view(user: UserModel | null, model: ${modelName}): boolean {
|
|
54
80
|
return true
|
|
55
81
|
}
|
|
56
82
|
|
|
57
83
|
/**
|
|
58
|
-
* Determine if the user can create ${
|
|
84
|
+
* Determine if the user can create ${modelName.toLowerCase()}s.
|
|
59
85
|
*/
|
|
60
86
|
create(user: UserModel | null): boolean {
|
|
61
87
|
return user !== null
|
|
62
88
|
}
|
|
63
89
|
|
|
64
90
|
/**
|
|
65
|
-
* Determine if the user can update the ${
|
|
91
|
+
* Determine if the user can update the ${modelName.toLowerCase()}.
|
|
66
92
|
*/
|
|
67
|
-
update(user: UserModel | null, model: ${
|
|
93
|
+
update(user: UserModel | null, model: ${modelName}): boolean {
|
|
68
94
|
return user?.id === model.user_id
|
|
69
95
|
}
|
|
70
96
|
|
|
71
97
|
/**
|
|
72
|
-
* Determine if the user can delete the ${
|
|
98
|
+
* Determine if the user can delete the ${modelName.toLowerCase()}.
|
|
73
99
|
*/
|
|
74
|
-
delete(user: UserModel | null, model: ${
|
|
100
|
+
delete(user: UserModel | null, model: ${modelName}): boolean {
|
|
75
101
|
return user?.id === model.user_id
|
|
76
102
|
}
|
|
77
103
|
|
|
78
104
|
/**
|
|
79
|
-
* Determine if the user can restore the ${
|
|
105
|
+
* Determine if the user can restore the ${modelName.toLowerCase()}.
|
|
80
106
|
*/
|
|
81
|
-
restore(user: UserModel | null, model: ${
|
|
107
|
+
restore(user: UserModel | null, model: ${modelName}): boolean {
|
|
82
108
|
return user?.id === model.user_id
|
|
83
109
|
}
|
|
84
110
|
|
|
85
111
|
/**
|
|
86
|
-
* Determine if the user can permanently delete the ${
|
|
112
|
+
* Determine if the user can permanently delete the ${modelName.toLowerCase()}.
|
|
87
113
|
*/
|
|
88
|
-
forceDelete(user: UserModel | null, model: ${
|
|
114
|
+
forceDelete(user: UserModel | null, model: ${modelName}): boolean {
|
|
89
115
|
return user?.id === model.user_id
|
|
90
116
|
}
|
|
91
117
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
118
|
+
`;
|
|
119
|
+
}
|
|
120
|
+
async function registerPolicy(modelName, policyName) {
|
|
121
|
+
const gatesPath = p.appPath("Gates.ts");
|
|
122
|
+
try {
|
|
123
|
+
let content = await get(gatesPath);
|
|
124
|
+
const policiesMatch = content.match(/export const policies[^{]*\{([^}]*)\}/);
|
|
125
|
+
if (policiesMatch) {
|
|
126
|
+
const existingPolicies = policiesMatch[1] ?? "";
|
|
127
|
+
if (existingPolicies.includes(`'${modelName}'`)) {
|
|
128
|
+
log.info(`Policy for '${modelName}' already registered in Gates.ts`);
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
const newPolicy = ` '${modelName}': '${policyName}',
|
|
132
|
+
`, updatedPolicies = `${existingPolicies.trimEnd()}
|
|
133
|
+
${newPolicy}`;
|
|
134
|
+
content = content.replace(/export const policies[^{]*\{([^}]*)\}/, `export const policies: Record<string, string | { policy: string, model?: string }> = {${updatedPolicies}}`);
|
|
135
|
+
await writeFile(gatesPath, content);
|
|
136
|
+
log.success(`Registered policy '${policyName}' for '${modelName}' in Gates.ts`);
|
|
137
|
+
}
|
|
138
|
+
} catch (error) {
|
|
139
|
+
log.warn(`Could not register policy in Gates.ts: ${error.message}`);
|
|
140
|
+
log.info("You may need to manually add it to app/Gates.ts");
|
|
141
|
+
}
|
|
142
|
+
}
|
package/dist/make-resource.js
CHANGED
|
@@ -1,27 +1,51 @@
|
|
|
1
|
-
|
|
2
|
-
import
|
|
1
|
+
import { log } from "@stacksjs/logging";
|
|
2
|
+
import * as p from "@stacksjs/path";
|
|
3
|
+
import { doesExist, writeFile } from "@stacksjs/storage";
|
|
4
|
+
export async function makeResource(options) {
|
|
5
|
+
const name = options.name;
|
|
6
|
+
if (!name) {
|
|
7
|
+
log.error("Resource name is required");
|
|
8
|
+
return !1;
|
|
9
|
+
}
|
|
10
|
+
const resourceName = name.endsWith("Resource") ? name : `${name}Resource`, modelName = options.model || resourceName.replace("Resource", ""), content = options.collection ? generateCollectionResourceContent(resourceName, modelName) : generateResourceContent(resourceName, modelName), resourcesDir = p.appPath("Resources");
|
|
11
|
+
if (!doesExist(resourcesDir)) {
|
|
12
|
+
const { mkdirSync } = await import("@stacksjs/storage");
|
|
13
|
+
mkdirSync(resourcesDir, { recursive: !0 });
|
|
14
|
+
}
|
|
15
|
+
const filePath = p.appPath(`Resources/${resourceName}.ts`);
|
|
16
|
+
try {
|
|
17
|
+
await writeFile(filePath, content);
|
|
18
|
+
log.success(`Created resource: ${filePath}`);
|
|
19
|
+
return !0;
|
|
20
|
+
} catch (error) {
|
|
21
|
+
log.error(`Failed to create resource: ${error.message}`);
|
|
22
|
+
return !1;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function generateResourceContent(resourceName, modelName) {
|
|
26
|
+
return `import type { Request } from '@stacksjs/router'
|
|
3
27
|
import { JsonResource } from '@stacksjs/api'
|
|
4
28
|
|
|
5
29
|
/**
|
|
6
|
-
* ${
|
|
30
|
+
* ${resourceName}
|
|
7
31
|
*
|
|
8
|
-
* Transforms ${
|
|
32
|
+
* Transforms ${modelName} model data for API responses.
|
|
9
33
|
*
|
|
10
34
|
* @example
|
|
11
35
|
* // Single resource
|
|
12
|
-
* return new ${
|
|
36
|
+
* return new ${resourceName}(${modelName.toLowerCase()}).toResponse()
|
|
13
37
|
*
|
|
14
38
|
* // Collection
|
|
15
|
-
* return ${
|
|
39
|
+
* return ${resourceName}.collection(${modelName.toLowerCase()}s).toResponse()
|
|
16
40
|
*
|
|
17
41
|
* // With additional data
|
|
18
|
-
* return new ${
|
|
42
|
+
* return new ${resourceName}(${modelName.toLowerCase()})
|
|
19
43
|
* .withAdditional({ message: 'Success' })
|
|
20
44
|
* .toResponse()
|
|
21
45
|
*/
|
|
22
46
|
|
|
23
|
-
// TODO: Import or define your ${
|
|
24
|
-
interface ${
|
|
47
|
+
// TODO: Import or define your ${modelName} type
|
|
48
|
+
interface ${modelName} {
|
|
25
49
|
id: number
|
|
26
50
|
name: string
|
|
27
51
|
created_at: Date
|
|
@@ -29,7 +53,7 @@ interface ${j} {
|
|
|
29
53
|
// Add your model properties here
|
|
30
54
|
}
|
|
31
55
|
|
|
32
|
-
export default class ${
|
|
56
|
+
export default class ${resourceName} extends JsonResource<${modelName}> {
|
|
33
57
|
/**
|
|
34
58
|
* Transform the resource into an array.
|
|
35
59
|
*/
|
|
@@ -55,20 +79,24 @@ export default class ${q} extends JsonResource<${j}> {
|
|
|
55
79
|
}
|
|
56
80
|
}
|
|
57
81
|
}
|
|
58
|
-
|
|
82
|
+
`;
|
|
83
|
+
}
|
|
84
|
+
function generateCollectionResourceContent(resourceName, modelName) {
|
|
85
|
+
const baseResourceName = resourceName.replace("Collection", "");
|
|
86
|
+
return `import type { Request } from '@stacksjs/router'
|
|
59
87
|
import { JsonResource, ResourceCollection } from '@stacksjs/api'
|
|
60
88
|
|
|
61
89
|
/**
|
|
62
|
-
* ${
|
|
90
|
+
* ${resourceName}
|
|
63
91
|
*
|
|
64
|
-
* Collection resource for ${
|
|
92
|
+
* Collection resource for ${modelName} with custom collection behavior.
|
|
65
93
|
*
|
|
66
94
|
* @example
|
|
67
|
-
* return new ${
|
|
95
|
+
* return new ${resourceName}(${modelName.toLowerCase()}s).toResponse()
|
|
68
96
|
*/
|
|
69
97
|
|
|
70
|
-
// TODO: Import or define your ${
|
|
71
|
-
interface ${
|
|
98
|
+
// TODO: Import or define your ${modelName} type
|
|
99
|
+
interface ${modelName} {
|
|
72
100
|
id: number
|
|
73
101
|
name: string
|
|
74
102
|
created_at: Date
|
|
@@ -78,7 +106,7 @@ interface ${j} {
|
|
|
78
106
|
/**
|
|
79
107
|
* Individual resource for the collection
|
|
80
108
|
*/
|
|
81
|
-
class ${
|
|
109
|
+
class ${baseResourceName} extends JsonResource<${modelName}> {
|
|
82
110
|
toArray(request?: Request): Record<string, any> {
|
|
83
111
|
return {
|
|
84
112
|
id: this.resource.id,
|
|
@@ -92,9 +120,9 @@ class ${v} extends JsonResource<${j}> {
|
|
|
92
120
|
/**
|
|
93
121
|
* Collection resource with custom behavior
|
|
94
122
|
*/
|
|
95
|
-
export default class ${
|
|
96
|
-
constructor(resources: ${
|
|
97
|
-
super(resources, ${
|
|
123
|
+
export default class ${resourceName} extends ResourceCollection<${modelName}, ${baseResourceName}> {
|
|
124
|
+
constructor(resources: ${modelName}[]) {
|
|
125
|
+
super(resources, ${baseResourceName})
|
|
98
126
|
}
|
|
99
127
|
|
|
100
128
|
/**
|
|
@@ -110,4 +138,5 @@ export default class ${q} extends ResourceCollection<${j}, ${v}> {
|
|
|
110
138
|
}
|
|
111
139
|
}
|
|
112
140
|
}
|
|
113
|
-
|
|
141
|
+
`;
|
|
142
|
+
}
|
package/dist/make.js
CHANGED
|
@@ -1,2 +1,328 @@
|
|
|
1
|
-
|
|
2
|
-
import{
|
|
1
|
+
import process from "node:process";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { italic, runCommand } from "@stacksjs/cli";
|
|
4
|
+
import { ExitCode } from "@stacksjs/types";
|
|
5
|
+
import { localUrl } from "@stacksjs/config";
|
|
6
|
+
import { Action } from "@stacksjs/enums";
|
|
7
|
+
import { handleError } from "@stacksjs/error-handling";
|
|
8
|
+
import { log } from "@stacksjs/logging";
|
|
9
|
+
import { frameworkPath, path as p, resolve } from "@stacksjs/path";
|
|
10
|
+
import { createFolder, doesFolderExist, writeTextFile } from "@stacksjs/storage";
|
|
11
|
+
import { kebabCase, pascalCase, template } from "@stacksjs/strings";
|
|
12
|
+
import { runAction } from "./helpers";
|
|
13
|
+
import { CODE_TEMPLATES } from "./templates";
|
|
14
|
+
let isDryRun = !1;
|
|
15
|
+
export function setDryRun(enabled) {
|
|
16
|
+
isDryRun = Boolean(enabled);
|
|
17
|
+
}
|
|
18
|
+
export function isDryRunActive() {
|
|
19
|
+
return isDryRun;
|
|
20
|
+
}
|
|
21
|
+
function generateCode(templateKey, ...args) {
|
|
22
|
+
return template(CODE_TEMPLATES[templateKey], ...args);
|
|
23
|
+
}
|
|
24
|
+
async function writeOrPreview(path, data) {
|
|
25
|
+
if (isDryRun) {
|
|
26
|
+
const lines = data.split(`
|
|
27
|
+
`), preview = lines.slice(0, 60).map((l) => ` ${l}`).join(`
|
|
28
|
+
`), truncated = lines.length > 60 ? `
|
|
29
|
+
${italic(`\u2026 ${lines.length - 60} more lines elided`)}` : "";
|
|
30
|
+
log.info(`[dry-run] would write ${path} (${lines.length} lines)`);
|
|
31
|
+
process.stdout.write(`${preview}${truncated}
|
|
32
|
+
|
|
33
|
+
`);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
await writeTextFile({ path, data });
|
|
37
|
+
}
|
|
38
|
+
async function createFileWithTemplate(path, templateKey, ...args) {
|
|
39
|
+
await writeOrPreview(path, generateCode(templateKey, ...args));
|
|
40
|
+
}
|
|
41
|
+
export async function invoke(options) {
|
|
42
|
+
if (options.component)
|
|
43
|
+
await makeComponent(options);
|
|
44
|
+
if (options.database)
|
|
45
|
+
makeDatabase(options);
|
|
46
|
+
if (options.function)
|
|
47
|
+
await makeFunction(options);
|
|
48
|
+
if (options.language)
|
|
49
|
+
await makeLanguage(options);
|
|
50
|
+
if (options.middleware)
|
|
51
|
+
await createMiddleware(options);
|
|
52
|
+
if (options.notification)
|
|
53
|
+
await makeNotification(options);
|
|
54
|
+
if (options.page)
|
|
55
|
+
await makePage(options);
|
|
56
|
+
if (options.stack)
|
|
57
|
+
await makeStack(options);
|
|
58
|
+
}
|
|
59
|
+
export async function make(options) {
|
|
60
|
+
return await invoke(options);
|
|
61
|
+
}
|
|
62
|
+
export async function makeAction(options) {
|
|
63
|
+
try {
|
|
64
|
+
const name = options.name;
|
|
65
|
+
log.info("Creating your action...");
|
|
66
|
+
await createAction(options);
|
|
67
|
+
log.success(`Created ${italic(name)} action`);
|
|
68
|
+
} catch (error) {
|
|
69
|
+
log.error("There was an error creating your action", error);
|
|
70
|
+
process.exit(ExitCode.FatalError);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
export async function makeComponent(options) {
|
|
74
|
+
try {
|
|
75
|
+
const name = options.name;
|
|
76
|
+
log.info("Creating your component...");
|
|
77
|
+
await createComponent(options);
|
|
78
|
+
log.success(`Created ${italic(name)} component`);
|
|
79
|
+
} catch (error) {
|
|
80
|
+
log.error("There was an error creating your component", error);
|
|
81
|
+
process.exit(ExitCode.FatalError);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
export async function createAction(options) {
|
|
85
|
+
const name = options.name, opts = options, wantsValidation = Boolean(opts.withValidation || options["with-validation"]), wantsAuth = Boolean(opts.withAuth || options["with-auth"]), templateKey = wantsValidation && wantsAuth ? "actionWithBoth" : wantsValidation ? "actionWithValidation" : wantsAuth ? "actionWithAuth" : "action";
|
|
86
|
+
await createFileWithTemplate(p.userActionsPath(name), templateKey, name);
|
|
87
|
+
}
|
|
88
|
+
export async function createComponent(options) {
|
|
89
|
+
const name = options.name;
|
|
90
|
+
await createFileWithTemplate(p.userComponentsPath(`${name}.vue`), "component", name);
|
|
91
|
+
}
|
|
92
|
+
export function makeDatabase(options) {
|
|
93
|
+
try {
|
|
94
|
+
const name = options.name;
|
|
95
|
+
log.info(`Creating your ${italic(name)} database...`);
|
|
96
|
+
createDatabase(options);
|
|
97
|
+
log.success(`Created ${italic(name)} database`);
|
|
98
|
+
} catch (error) {
|
|
99
|
+
log.error("There was an error creating your database", error);
|
|
100
|
+
process.exit(ExitCode.FatalError);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
export function createDatabase(options) {
|
|
104
|
+
log.debug("createDatabase options", options);
|
|
105
|
+
}
|
|
106
|
+
export function factory(options) {
|
|
107
|
+
try {
|
|
108
|
+
const name = options.name;
|
|
109
|
+
log.info(`Creating your ${italic(name)} factory...`);
|
|
110
|
+
createDatabase(options);
|
|
111
|
+
log.success(`Created ${italic(name)} factory`);
|
|
112
|
+
} catch (error) {
|
|
113
|
+
log.error("There was an error creating your factory", error);
|
|
114
|
+
process.exit(ExitCode.FatalError);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
export async function createFactory(options) {
|
|
118
|
+
const name = options.name || "MyFactory", factoryName = name.endsWith("Factory") ? name : `${name}Factory`;
|
|
119
|
+
try {
|
|
120
|
+
const { path: p } = await import("@stacksjs/path"), factoryPath = `${p.userDatabasePath("factories")}/${factoryName}.ts`;
|
|
121
|
+
if (await Bun.file(factoryPath).exists()) {
|
|
122
|
+
log.warn(`Factory ${factoryName} already exists at ${factoryPath}`);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
const modelName = name.replace(/Factory$/, ""), content = `import type { ${modelName}Model } from '@stacksjs/orm'
|
|
126
|
+
|
|
127
|
+
export function ${factoryName}(): Partial<${modelName}Model> {
|
|
128
|
+
return {
|
|
129
|
+
// Define your factory attributes here
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export default ${factoryName}
|
|
134
|
+
`;
|
|
135
|
+
await writeOrPreview(factoryPath, content);
|
|
136
|
+
log.success(`Created factory: ${factoryPath}`);
|
|
137
|
+
} catch (error) {
|
|
138
|
+
log.error(`Failed to create factory ${factoryName}`, error);
|
|
139
|
+
throw error;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
export async function makeNotification(options) {
|
|
143
|
+
try {
|
|
144
|
+
const name = options.name;
|
|
145
|
+
log.info(`Creating your ${italic(name)} notification...`);
|
|
146
|
+
await createNotification(options);
|
|
147
|
+
log.success(`Created ${italic(name)} notification`);
|
|
148
|
+
} catch (error) {
|
|
149
|
+
log.error("There was an error creating your notification", error);
|
|
150
|
+
process.exit(ExitCode.FatalError);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
export async function createMail(options) {
|
|
154
|
+
const rawName = String(options.name || "").trim();
|
|
155
|
+
if (!rawName) {
|
|
156
|
+
log.error("A mail name is required (e.g. `buddy make:mail OrderShipped`).");
|
|
157
|
+
return !1;
|
|
158
|
+
}
|
|
159
|
+
const className = pascalCase(rawName), kebabName = kebabCase(rawName), mailPath = p.userMailPath(`${className}.ts`), templatePath = p.userEmailsPath(`${kebabName}.stx`);
|
|
160
|
+
if (!options.force) {
|
|
161
|
+
if (existsSync(mailPath)) {
|
|
162
|
+
console.error(`${italic(mailPath)} already exists \u2014 re-run with --force to overwrite.`);
|
|
163
|
+
return !1;
|
|
164
|
+
}
|
|
165
|
+
if (existsSync(templatePath)) {
|
|
166
|
+
console.error(`${italic(templatePath)} already exists \u2014 re-run with --force to overwrite.`);
|
|
167
|
+
return !1;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
if (!doesFolderExist(p.userMailPath()))
|
|
171
|
+
await createFolder(p.userMailPath());
|
|
172
|
+
if (!doesFolderExist(p.userEmailsPath()))
|
|
173
|
+
await createFolder(p.userEmailsPath());
|
|
174
|
+
try {
|
|
175
|
+
await createFileWithTemplate(mailPath, "mail", className, kebabName);
|
|
176
|
+
await createFileWithTemplate(templatePath, "mailTemplate", className, kebabName);
|
|
177
|
+
return !0;
|
|
178
|
+
} catch (error) {
|
|
179
|
+
handleError("Error creating mailable", error);
|
|
180
|
+
return !1;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
export async function makeMail(options) {
|
|
184
|
+
try {
|
|
185
|
+
const name = options.name;
|
|
186
|
+
log.info(`Creating your ${italic(name)} mailable...`);
|
|
187
|
+
if (!await createMail(options))
|
|
188
|
+
process.exit(ExitCode.FatalError);
|
|
189
|
+
log.success(`Created ${italic(pascalCase(name))} mailable + ${italic(kebabCase(name))}.stx template`);
|
|
190
|
+
} catch (error) {
|
|
191
|
+
log.error("There was an error creating your mailable", error);
|
|
192
|
+
process.exit(ExitCode.FatalError);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
export async function makePage(options) {
|
|
196
|
+
try {
|
|
197
|
+
const name = options.name;
|
|
198
|
+
log.info("Creating your page...");
|
|
199
|
+
await createPage(options);
|
|
200
|
+
log.success(`Created ${name} page`);
|
|
201
|
+
} catch (error) {
|
|
202
|
+
log.error("There was an error creating your page", error);
|
|
203
|
+
process.exit(ExitCode.FatalError);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
export async function createPage(options) {
|
|
207
|
+
const name = options.name;
|
|
208
|
+
await createFileWithTemplate(p.userViewsPath(`${name}.vue`), "page", name);
|
|
209
|
+
}
|
|
210
|
+
export async function makeFunction(options) {
|
|
211
|
+
try {
|
|
212
|
+
const name = options.name;
|
|
213
|
+
log.info("Creating your function...");
|
|
214
|
+
await createFunction(options);
|
|
215
|
+
log.success(`Created ${name} function`);
|
|
216
|
+
} catch (error) {
|
|
217
|
+
log.error("There was an error creating your function", error);
|
|
218
|
+
process.exit(ExitCode.FatalError);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
export async function createFunction(options) {
|
|
222
|
+
const name = options.name;
|
|
223
|
+
await createFileWithTemplate(p.userFunctionsPath(`${name}.ts`), "function", name);
|
|
224
|
+
}
|
|
225
|
+
export async function makeLanguage(options) {
|
|
226
|
+
try {
|
|
227
|
+
const name = options.name;
|
|
228
|
+
log.info("Creating your translation file...");
|
|
229
|
+
await createLanguage(options);
|
|
230
|
+
log.success(`Created ${name} translation file`);
|
|
231
|
+
} catch (error) {
|
|
232
|
+
log.error("There was an error creating your language.", error);
|
|
233
|
+
process.exit(ExitCode.FatalError);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
export async function createLanguage(options) {
|
|
237
|
+
const name = options.name;
|
|
238
|
+
await createFileWithTemplate(p.resourcesPath(`lang/${name}.yml`), "language", name);
|
|
239
|
+
}
|
|
240
|
+
export async function makeStack(options) {
|
|
241
|
+
try {
|
|
242
|
+
const name = options.name;
|
|
243
|
+
log.info(`Creating your ${name} stack...`);
|
|
244
|
+
const stackDir = resolve(process.cwd(), name);
|
|
245
|
+
if (doesFolderExist(stackDir)) {
|
|
246
|
+
log.error(`Directory "${name}" already exists`);
|
|
247
|
+
process.exit(ExitCode.FatalError);
|
|
248
|
+
}
|
|
249
|
+
await createFolder(stackDir);
|
|
250
|
+
const dirs = ["app/Actions", "app/Models", "config", "database/migrations", "resources/views", "resources/components", "resources/functions", "routes", "public"];
|
|
251
|
+
for (const dir of dirs)
|
|
252
|
+
await createFolder(resolve(stackDir, dir));
|
|
253
|
+
const shortName = name.replace(/^@[^/]+\//, "").replace(/-stack$/, "");
|
|
254
|
+
await createFileWithTemplate(resolve(stackDir, "package.json"), "stackPackageJson", name, shortName);
|
|
255
|
+
log.success(`Successfully scaffolded your "${name}" stack`);
|
|
256
|
+
log.info("");
|
|
257
|
+
log.info(` cd ${name}`);
|
|
258
|
+
log.info(" # Add your models, actions, views, etc.");
|
|
259
|
+
log.info(" # Then publish: bun publish");
|
|
260
|
+
log.info("");
|
|
261
|
+
log.info(" Users install it with:");
|
|
262
|
+
log.info(` buddy stack:install ${name}`);
|
|
263
|
+
} catch (error) {
|
|
264
|
+
log.error("There was an error creating your stack", error);
|
|
265
|
+
process.exit(ExitCode.FatalError);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
export async function createNotification(options) {
|
|
269
|
+
const name = options.name;
|
|
270
|
+
try {
|
|
271
|
+
let importOption = "EmailOptions";
|
|
272
|
+
if (!doesFolderExist("notifications"))
|
|
273
|
+
await createFolder("./notifications");
|
|
274
|
+
if (options.chat)
|
|
275
|
+
importOption = "ChatOptions";
|
|
276
|
+
if (options.sms)
|
|
277
|
+
importOption = "SMSOptions";
|
|
278
|
+
await createFileWithTemplate(p.userNotificationsPath(`${name}.ts`), "notification", importOption);
|
|
279
|
+
return !0;
|
|
280
|
+
} catch (error) {
|
|
281
|
+
handleError("Error creating notification", error);
|
|
282
|
+
return !1;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
export async function createMigration(options) {
|
|
286
|
+
const optionName = options.name, table = "dummy-name";
|
|
287
|
+
if (!optionName[0])
|
|
288
|
+
throw Error("options.name is required and cannot be empty");
|
|
289
|
+
const name = optionName[0].toUpperCase() + optionName.slice(1), path = frameworkPath(`database/migrations/${name}.ts`);
|
|
290
|
+
try {
|
|
291
|
+
await createFileWithTemplate(path, "migration", table);
|
|
292
|
+
log.success(`Successfully created your migration file at stacks/database/migrations/${name}.ts`);
|
|
293
|
+
} catch (error) {
|
|
294
|
+
log.error(error);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
export async function makeQueueTable() {
|
|
298
|
+
await runAction(Action.QueueTable);
|
|
299
|
+
}
|
|
300
|
+
export async function makeCertificate() {
|
|
301
|
+
const domain = await localUrl();
|
|
302
|
+
log.info("Creating SSL certificate...");
|
|
303
|
+
await runCommand(`tlsx ${domain}`, {
|
|
304
|
+
cwd: p.storagePath("keys")
|
|
305
|
+
});
|
|
306
|
+
log.success("Certificate created");
|
|
307
|
+
log.info("Installing SSL certificate...");
|
|
308
|
+
await runCommand("tlsx -install", {
|
|
309
|
+
cwd: p.storagePath("keys")
|
|
310
|
+
});
|
|
311
|
+
log.success("Certificate installed");
|
|
312
|
+
}
|
|
313
|
+
export async function createModel(options) {
|
|
314
|
+
const optionName = options.name;
|
|
315
|
+
if (!optionName[0])
|
|
316
|
+
throw Error("options.name is required and cannot be empty");
|
|
317
|
+
const name = optionName[0].toUpperCase() + optionName.slice(1), tableName = name.replace(/([A-Z])/g, "_$1").toLowerCase().replace(/^_/, "").replace(/([^s])$/, "$1s"), path = p.userModelsPath(`${name}.ts`);
|
|
318
|
+
try {
|
|
319
|
+
await createFileWithTemplate(path, "model", name, tableName);
|
|
320
|
+
log.success(`Model created: ${italic(`app/Models/${name}.ts`)}`);
|
|
321
|
+
} catch (error) {
|
|
322
|
+
log.error(error);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
export async function createMiddleware(options) {
|
|
326
|
+
const name = options.name;
|
|
327
|
+
await createFileWithTemplate(p.userMiddlewarePath(`${name}.ts`), "middleware", name);
|
|
328
|
+
}
|
package/dist/migrate/database.js
CHANGED
|
@@ -1,2 +1,16 @@
|
|
|
1
|
-
|
|
2
|
-
import
|
|
1
|
+
import process from "node:process";
|
|
2
|
+
import { generateMigrations, runDatabaseMigration } from "@stacksjs/database";
|
|
3
|
+
import { log } from "@stacksjs/logging";
|
|
4
|
+
const result = await generateMigrations();
|
|
5
|
+
if (result?.isErr) {
|
|
6
|
+
console.error(result.error);
|
|
7
|
+
log.error("[stacks] generateMigrations failed", result.error);
|
|
8
|
+
process.exit(1);
|
|
9
|
+
}
|
|
10
|
+
const migrateResult = await runDatabaseMigration();
|
|
11
|
+
if (migrateResult.isErr) {
|
|
12
|
+
log.error("runDatabaseMigration failed");
|
|
13
|
+
log.error(migrateResult.error);
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
process.exit(0);
|
package/dist/migrate/fresh.js
CHANGED
|
@@ -1,2 +1,31 @@
|
|
|
1
|
-
|
|
2
|
-
import
|
|
1
|
+
import process from "node:process";
|
|
2
|
+
import { generateMigrations, migrateAuthTables, migrateNotificationTables, migrateRbacTables, resetDatabase, runDatabaseMigration } from "@stacksjs/database";
|
|
3
|
+
import { log } from "@stacksjs/logging";
|
|
4
|
+
const resetResult = await resetDatabase();
|
|
5
|
+
if (resetResult?.isErr) {
|
|
6
|
+
console.error(resetResult.error);
|
|
7
|
+
log.error("resetDatabase failed", resetResult.error);
|
|
8
|
+
process.exit(1);
|
|
9
|
+
}
|
|
10
|
+
const genResult = await generateMigrations();
|
|
11
|
+
if (genResult?.isErr) {
|
|
12
|
+
console.error(genResult.error);
|
|
13
|
+
log.error("generateMigrations failed", genResult.error);
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
const authResult = await migrateAuthTables();
|
|
17
|
+
if (!authResult.success)
|
|
18
|
+
log.error(`Failed to migrate auth tables: ${authResult.error}`);
|
|
19
|
+
const notifResult = await migrateNotificationTables();
|
|
20
|
+
if (!notifResult.success)
|
|
21
|
+
log.error(`Failed to migrate notification tables: ${notifResult.error}`);
|
|
22
|
+
const rbacResult = await migrateRbacTables();
|
|
23
|
+
if (!rbacResult.success)
|
|
24
|
+
log.error(`Failed to migrate RBAC tables: ${rbacResult.error}`);
|
|
25
|
+
const migrateResult = await runDatabaseMigration();
|
|
26
|
+
if (migrateResult.isErr) {
|
|
27
|
+
log.error("runDatabaseMigration failed");
|
|
28
|
+
log.error(migrateResult.error);
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
process.exit(0);
|