@classytic/arc 1.1.0 → 2.1.3
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/README.md +247 -794
- package/bin/arc.js +91 -52
- package/dist/EventTransport-BkUDYZEb.d.mts +99 -0
- package/dist/HookSystem-BsGV-j2l.mjs +404 -0
- package/dist/ResourceRegistry-7Ic20ZMw.mjs +249 -0
- package/dist/adapters/index.d.mts +5 -0
- package/dist/adapters/index.mjs +3 -0
- package/dist/audit/index.d.mts +81 -0
- package/dist/audit/index.mjs +275 -0
- package/dist/audit/mongodb.d.mts +5 -0
- package/dist/audit/mongodb.mjs +3 -0
- package/dist/audited-CGdLiSlE.mjs +140 -0
- package/dist/auth/index.d.mts +188 -0
- package/dist/auth/index.mjs +1096 -0
- package/dist/auth/redis-session.d.mts +43 -0
- package/dist/auth/redis-session.mjs +75 -0
- package/dist/betterAuthOpenApi-DjWDddNc.mjs +249 -0
- package/dist/cache/index.d.mts +145 -0
- package/dist/cache/index.mjs +91 -0
- package/dist/caching-GSDJcA6-.mjs +93 -0
- package/dist/chunk-C7Uep-_p.mjs +20 -0
- package/dist/circuitBreaker-DYhWBW_D.mjs +1096 -0
- package/dist/cli/commands/describe.d.mts +18 -0
- package/dist/cli/commands/describe.mjs +238 -0
- package/dist/cli/commands/docs.d.mts +13 -0
- package/dist/cli/commands/docs.mjs +52 -0
- package/dist/cli/commands/{generate.d.ts → generate.d.mts} +3 -2
- package/dist/cli/commands/generate.mjs +357 -0
- package/dist/cli/commands/{init.d.ts → init.d.mts} +11 -8
- package/dist/cli/commands/{init.js → init.mjs} +807 -617
- package/dist/cli/commands/introspect.d.mts +10 -0
- package/dist/cli/commands/introspect.mjs +75 -0
- package/dist/cli/index.d.mts +16 -0
- package/dist/cli/index.mjs +156 -0
- package/dist/constants-DdXFXQtN.mjs +84 -0
- package/dist/core/index.d.mts +5 -0
- package/dist/core/index.mjs +4 -0
- package/dist/createApp-D2D5XXaV.mjs +559 -0
- package/dist/defineResource-PXzSJ15_.mjs +2197 -0
- package/dist/discovery/index.d.mts +46 -0
- package/dist/discovery/index.mjs +109 -0
- package/dist/docs/index.d.mts +162 -0
- package/dist/docs/index.mjs +74 -0
- package/dist/elevation-DGo5shaX.d.mts +87 -0
- package/dist/elevation-DSTbVvYj.mjs +113 -0
- package/dist/errorHandler-C3GY3_ow.mjs +108 -0
- package/dist/errorHandler-CW3OOeYq.d.mts +72 -0
- package/dist/errors-DAWRdiYP.d.mts +124 -0
- package/dist/errors-DBANPbGr.mjs +211 -0
- package/dist/eventPlugin-BEOvaDqo.mjs +229 -0
- package/dist/eventPlugin-H6wDDjGO.d.mts +124 -0
- package/dist/events/index.d.mts +53 -0
- package/dist/events/index.mjs +51 -0
- package/dist/events/transports/redis-stream-entry.d.mts +2 -0
- package/dist/events/transports/redis-stream-entry.mjs +177 -0
- package/dist/events/transports/redis.d.mts +76 -0
- package/dist/events/transports/redis.mjs +124 -0
- package/dist/externalPaths-SyPF2tgK.d.mts +50 -0
- package/dist/factory/index.d.mts +63 -0
- package/dist/factory/index.mjs +3 -0
- package/dist/fastifyAdapter-C8DlE0YH.d.mts +216 -0
- package/dist/fields-Bi_AVKSo.d.mts +109 -0
- package/dist/fields-CTd_CrKr.mjs +114 -0
- package/dist/hooks/index.d.mts +4 -0
- package/dist/hooks/index.mjs +3 -0
- package/dist/idempotency/index.d.mts +96 -0
- package/dist/idempotency/index.mjs +319 -0
- package/dist/idempotency/mongodb.d.mts +2 -0
- package/dist/idempotency/mongodb.mjs +114 -0
- package/dist/idempotency/redis.d.mts +2 -0
- package/dist/idempotency/redis.mjs +103 -0
- package/dist/index.d.mts +260 -0
- package/dist/index.mjs +104 -0
- package/dist/integrations/event-gateway.d.mts +46 -0
- package/dist/integrations/event-gateway.mjs +43 -0
- package/dist/integrations/index.d.mts +5 -0
- package/dist/integrations/index.mjs +1 -0
- package/dist/integrations/jobs.d.mts +103 -0
- package/dist/integrations/jobs.mjs +123 -0
- package/dist/integrations/streamline.d.mts +60 -0
- package/dist/integrations/streamline.mjs +125 -0
- package/dist/integrations/websocket.d.mts +82 -0
- package/dist/integrations/websocket.mjs +288 -0
- package/dist/interface-CSNjltAc.d.mts +77 -0
- package/dist/interface-DTbsvIWe.d.mts +54 -0
- package/dist/interface-e9XfSsUV.d.mts +1097 -0
- package/dist/introspectionPlugin-B3JkrjwU.mjs +53 -0
- package/dist/keys-DhqDRxv3.mjs +42 -0
- package/dist/logger-ByrvQWZO.mjs +78 -0
- package/dist/memory-B2v7KrCB.mjs +143 -0
- package/dist/migrations/index.d.mts +156 -0
- package/dist/migrations/index.mjs +260 -0
- package/dist/mongodb-ClykrfGo.d.mts +118 -0
- package/dist/mongodb-DNKEExbf.mjs +93 -0
- package/dist/mongodb-Dg8O_gvd.d.mts +71 -0
- package/dist/openapi-9nB_kiuR.mjs +525 -0
- package/dist/org/index.d.mts +68 -0
- package/dist/org/index.mjs +513 -0
- package/dist/org/types.d.mts +82 -0
- package/dist/org/types.mjs +1 -0
- package/dist/permissions/index.d.mts +278 -0
- package/dist/permissions/index.mjs +579 -0
- package/dist/plugins/index.d.mts +172 -0
- package/dist/plugins/index.mjs +522 -0
- package/dist/plugins/response-cache.d.mts +87 -0
- package/dist/plugins/response-cache.mjs +283 -0
- package/dist/plugins/tracing-entry.d.mts +2 -0
- package/dist/plugins/tracing-entry.mjs +185 -0
- package/dist/pluralize-CM-jZg7p.mjs +86 -0
- package/dist/policies/{index.d.ts → index.d.mts} +204 -170
- package/dist/policies/index.mjs +321 -0
- package/dist/presets/{index.d.ts → index.d.mts} +62 -131
- package/dist/presets/index.mjs +143 -0
- package/dist/presets/multiTenant.d.mts +24 -0
- package/dist/presets/multiTenant.mjs +113 -0
- package/dist/presets-BTeYbw7h.d.mts +57 -0
- package/dist/presets-CeFtfDR8.mjs +119 -0
- package/dist/prisma-C3iornoK.d.mts +274 -0
- package/dist/prisma-DJbMt3yf.mjs +627 -0
- package/dist/queryCachePlugin-B6R0d4av.mjs +138 -0
- package/dist/queryCachePlugin-Q6SYuHZ6.d.mts +71 -0
- package/dist/redis-UwjEp8Ea.d.mts +49 -0
- package/dist/redis-stream-CBg0upHI.d.mts +103 -0
- package/dist/registry/index.d.mts +11 -0
- package/dist/registry/index.mjs +4 -0
- package/dist/requestContext-xi6OKBL-.mjs +55 -0
- package/dist/schemaConverter-Dtg0Kt9T.mjs +98 -0
- package/dist/schemas/index.d.mts +63 -0
- package/dist/schemas/index.mjs +82 -0
- package/dist/scope/index.d.mts +21 -0
- package/dist/scope/index.mjs +65 -0
- package/dist/sessionManager-D_iEHjQl.d.mts +186 -0
- package/dist/sse-DkqQ1uxb.mjs +123 -0
- package/dist/testing/index.d.mts +907 -0
- package/dist/testing/index.mjs +1976 -0
- package/dist/tracing-8CEbhF0w.d.mts +70 -0
- package/dist/typeGuards-DwxA1t_L.mjs +9 -0
- package/dist/types/index.d.mts +946 -0
- package/dist/types/index.mjs +14 -0
- package/dist/types-B0dhNrnd.d.mts +445 -0
- package/dist/types-Beqn1Un7.mjs +38 -0
- package/dist/types-DelU6kln.mjs +25 -0
- package/dist/types-RLkFVgaw.d.mts +101 -0
- package/dist/utils/index.d.mts +747 -0
- package/dist/utils/index.mjs +6 -0
- package/package.json +194 -68
- package/dist/BaseController-DVAiHxEQ.d.ts +0 -233
- package/dist/adapters/index.d.ts +0 -237
- package/dist/adapters/index.js +0 -668
- package/dist/arcCorePlugin-CsShQdyP.d.ts +0 -273
- package/dist/audit/index.d.ts +0 -195
- package/dist/audit/index.js +0 -319
- package/dist/auth/index.d.ts +0 -47
- package/dist/auth/index.js +0 -174
- package/dist/cli/commands/docs.d.ts +0 -11
- package/dist/cli/commands/docs.js +0 -474
- package/dist/cli/commands/generate.js +0 -334
- package/dist/cli/commands/introspect.d.ts +0 -8
- package/dist/cli/commands/introspect.js +0 -338
- package/dist/cli/index.d.ts +0 -4
- package/dist/cli/index.js +0 -3269
- package/dist/core/index.d.ts +0 -220
- package/dist/core/index.js +0 -2786
- package/dist/createApp-Ce9wl8W9.d.ts +0 -77
- package/dist/docs/index.d.ts +0 -166
- package/dist/docs/index.js +0 -658
- package/dist/errors-8WIxGS_6.d.ts +0 -122
- package/dist/events/index.d.ts +0 -117
- package/dist/events/index.js +0 -89
- package/dist/factory/index.d.ts +0 -38
- package/dist/factory/index.js +0 -1652
- package/dist/hooks/index.d.ts +0 -4
- package/dist/hooks/index.js +0 -199
- package/dist/idempotency/index.d.ts +0 -323
- package/dist/idempotency/index.js +0 -500
- package/dist/index-B4t03KQ0.d.ts +0 -1366
- package/dist/index.d.ts +0 -135
- package/dist/index.js +0 -4756
- package/dist/migrations/index.d.ts +0 -185
- package/dist/migrations/index.js +0 -274
- package/dist/org/index.d.ts +0 -129
- package/dist/org/index.js +0 -220
- package/dist/permissions/index.d.ts +0 -144
- package/dist/permissions/index.js +0 -103
- package/dist/plugins/index.d.ts +0 -46
- package/dist/plugins/index.js +0 -1069
- package/dist/policies/index.js +0 -196
- package/dist/presets/index.js +0 -384
- package/dist/presets/multiTenant.d.ts +0 -39
- package/dist/presets/multiTenant.js +0 -112
- package/dist/registry/index.d.ts +0 -16
- package/dist/registry/index.js +0 -253
- package/dist/testing/index.d.ts +0 -618
- package/dist/testing/index.js +0 -48020
- package/dist/types/index.d.ts +0 -4
- package/dist/types/index.js +0 -8
- package/dist/types-B99TBmFV.d.ts +0 -76
- package/dist/types-BvckRbs2.d.ts +0 -143
- package/dist/utils/index.d.ts +0 -679
- package/dist/utils/index.js +0 -931
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
//#region src/cli/commands/describe.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Arc CLI - Describe Command
|
|
4
|
+
*
|
|
5
|
+
* Machine-readable resource metadata for AI agents.
|
|
6
|
+
* Outputs JSON with fields, permissions, pipeline, routes, events —
|
|
7
|
+
* everything an LLM needs to understand and generate code for the API.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```bash
|
|
11
|
+
* arc describe ./src/resources.js --json
|
|
12
|
+
* arc describe ./src/resources.js --resource product
|
|
13
|
+
* arc describe ./src/resources.js --pretty
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
declare function describe(args: string[]): Promise<void>;
|
|
17
|
+
//#endregion
|
|
18
|
+
export { describe as default, describe };
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import { t as CRUD_OPERATIONS } from "../../constants-DdXFXQtN.mjs";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import { pathToFileURL } from "node:url";
|
|
4
|
+
|
|
5
|
+
//#region src/cli/commands/describe.ts
|
|
6
|
+
/**
|
|
7
|
+
* Arc CLI - Describe Command
|
|
8
|
+
*
|
|
9
|
+
* Machine-readable resource metadata for AI agents.
|
|
10
|
+
* Outputs JSON with fields, permissions, pipeline, routes, events —
|
|
11
|
+
* everything an LLM needs to understand and generate code for the API.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```bash
|
|
15
|
+
* arc describe ./src/resources.js --json
|
|
16
|
+
* arc describe ./src/resources.js --resource product
|
|
17
|
+
* arc describe ./src/resources.js --pretty
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
function describePermission(check) {
|
|
21
|
+
if (!check || typeof check !== "function") return { type: "custom" };
|
|
22
|
+
const fn = check;
|
|
23
|
+
if (fn._isPublic === true) return { type: "public" };
|
|
24
|
+
if (Array.isArray(fn._roles)) return {
|
|
25
|
+
type: "requireRoles",
|
|
26
|
+
roles: fn._roles
|
|
27
|
+
};
|
|
28
|
+
const src = check.toString();
|
|
29
|
+
if (src.includes("ctx.user") && !src.includes("roles") && src.length < 200) return { type: "requireAuth" };
|
|
30
|
+
return { type: "custom" };
|
|
31
|
+
}
|
|
32
|
+
function describePermissions(perms) {
|
|
33
|
+
if (!perms) return {};
|
|
34
|
+
const result = {};
|
|
35
|
+
for (const op of CRUD_OPERATIONS) {
|
|
36
|
+
const check = perms[op];
|
|
37
|
+
if (check) result[op] = describePermission(check);
|
|
38
|
+
}
|
|
39
|
+
return result;
|
|
40
|
+
}
|
|
41
|
+
function describeFields(fieldPerms) {
|
|
42
|
+
if (!fieldPerms || Object.keys(fieldPerms).length === 0) return void 0;
|
|
43
|
+
const result = {};
|
|
44
|
+
for (const [field, perm] of Object.entries(fieldPerms)) {
|
|
45
|
+
const desc = { type: perm._type };
|
|
46
|
+
if (perm.roles?.length) desc.roles = perm.roles;
|
|
47
|
+
if (perm.redactValue !== void 0) desc.redactValue = perm.redactValue;
|
|
48
|
+
result[field] = desc;
|
|
49
|
+
}
|
|
50
|
+
return result;
|
|
51
|
+
}
|
|
52
|
+
function describePipeline(pipe) {
|
|
53
|
+
if (!pipe) return void 0;
|
|
54
|
+
const steps = [];
|
|
55
|
+
if (Array.isArray(pipe)) steps.push(...pipe);
|
|
56
|
+
else {
|
|
57
|
+
const seen = /* @__PURE__ */ new Set();
|
|
58
|
+
for (const opSteps of Object.values(pipe)) if (Array.isArray(opSteps)) for (const step of opSteps) {
|
|
59
|
+
const key = `${step._type}:${step.name}`;
|
|
60
|
+
if (!seen.has(key)) {
|
|
61
|
+
seen.add(key);
|
|
62
|
+
steps.push(step);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if (steps.length === 0) return void 0;
|
|
67
|
+
const guards = [];
|
|
68
|
+
const transforms = [];
|
|
69
|
+
const interceptors = [];
|
|
70
|
+
for (const step of steps) {
|
|
71
|
+
const desc = { name: step.name };
|
|
72
|
+
if (step.operations?.length) desc.operations = [...step.operations];
|
|
73
|
+
switch (step._type) {
|
|
74
|
+
case "guard":
|
|
75
|
+
guards.push(desc);
|
|
76
|
+
break;
|
|
77
|
+
case "transform":
|
|
78
|
+
transforms.push(desc);
|
|
79
|
+
break;
|
|
80
|
+
case "interceptor":
|
|
81
|
+
interceptors.push(desc);
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return {
|
|
86
|
+
guards,
|
|
87
|
+
transforms,
|
|
88
|
+
interceptors
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
function describeRoutes(resource) {
|
|
92
|
+
const routes = [];
|
|
93
|
+
if (!resource.disableDefaultRoutes) {
|
|
94
|
+
const disabled = new Set(resource.disabledRoutes ?? []);
|
|
95
|
+
for (const { method, suffix, op } of [
|
|
96
|
+
{
|
|
97
|
+
method: "GET",
|
|
98
|
+
suffix: "",
|
|
99
|
+
op: "list"
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
method: "GET",
|
|
103
|
+
suffix: "/:id",
|
|
104
|
+
op: "get"
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
method: "POST",
|
|
108
|
+
suffix: "",
|
|
109
|
+
op: "create"
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
method: "PATCH",
|
|
113
|
+
suffix: "/:id",
|
|
114
|
+
op: "update"
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
method: "DELETE",
|
|
118
|
+
suffix: "/:id",
|
|
119
|
+
op: "delete"
|
|
120
|
+
}
|
|
121
|
+
]) if (!disabled.has(op)) {
|
|
122
|
+
const route = {
|
|
123
|
+
method,
|
|
124
|
+
path: `${resource.prefix}${suffix}`,
|
|
125
|
+
operation: op
|
|
126
|
+
};
|
|
127
|
+
const perm = resource.permissions[op];
|
|
128
|
+
if (perm) route.permission = describePermission(perm);
|
|
129
|
+
routes.push(route);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
for (const ar of resource.additionalRoutes) routes.push({
|
|
133
|
+
method: ar.method,
|
|
134
|
+
path: `${resource.prefix}${ar.path}`,
|
|
135
|
+
operation: typeof ar.handler === "string" ? ar.handler : "custom",
|
|
136
|
+
summary: ar.summary,
|
|
137
|
+
description: ar.description,
|
|
138
|
+
permission: describePermission(ar.permissions)
|
|
139
|
+
});
|
|
140
|
+
return routes;
|
|
141
|
+
}
|
|
142
|
+
function describeEvents(resourceName, events) {
|
|
143
|
+
if (!events) return [];
|
|
144
|
+
return Object.entries(events).map(([action, def]) => ({
|
|
145
|
+
name: `${resourceName}:${action}`,
|
|
146
|
+
description: def.description,
|
|
147
|
+
hasSchema: !!def.schema
|
|
148
|
+
}));
|
|
149
|
+
}
|
|
150
|
+
function describeMiddlewares(middlewares) {
|
|
151
|
+
if (!middlewares) return [];
|
|
152
|
+
const ops = [];
|
|
153
|
+
for (const [op, handlers] of Object.entries(middlewares)) if (handlers?.length) ops.push(`${op}(${handlers.length})`);
|
|
154
|
+
return ops;
|
|
155
|
+
}
|
|
156
|
+
function describeResource(resource, module) {
|
|
157
|
+
return {
|
|
158
|
+
name: resource.name,
|
|
159
|
+
displayName: resource.displayName,
|
|
160
|
+
prefix: resource.prefix,
|
|
161
|
+
tag: resource.tag,
|
|
162
|
+
module,
|
|
163
|
+
adapter: resource.adapter ? {
|
|
164
|
+
type: resource.adapter.type,
|
|
165
|
+
name: resource.adapter.name
|
|
166
|
+
} : null,
|
|
167
|
+
permissions: describePermissions(resource.permissions),
|
|
168
|
+
presets: resource._appliedPresets ?? [],
|
|
169
|
+
fields: describeFields(resource.fields),
|
|
170
|
+
pipeline: describePipeline(resource.pipe),
|
|
171
|
+
routes: describeRoutes(resource),
|
|
172
|
+
events: describeEvents(resource.name, resource.events),
|
|
173
|
+
schemaOptions: Object.keys(resource.schemaOptions ?? {}).length > 0 ? resource.schemaOptions : void 0,
|
|
174
|
+
rateLimit: resource.rateLimit,
|
|
175
|
+
middlewares: describeMiddlewares(resource.middlewares)
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
async function describe(args) {
|
|
179
|
+
try {
|
|
180
|
+
const flags = new Set(args.filter((a) => a.startsWith("--")));
|
|
181
|
+
const positional = args.filter((a) => !a.startsWith("--"));
|
|
182
|
+
const pretty = flags.has("--pretty") || !flags.has("--json");
|
|
183
|
+
const filterResource = positional[1];
|
|
184
|
+
const entryPath = positional[0];
|
|
185
|
+
if (!entryPath) {
|
|
186
|
+
console.log("Usage: arc describe <entry-file> [resource-name] [--json] [--pretty]\n");
|
|
187
|
+
console.log("Outputs machine-readable JSON metadata for AI agents.\n");
|
|
188
|
+
console.log("Options:");
|
|
189
|
+
console.log(" --json Output compact JSON (default if piped)");
|
|
190
|
+
console.log(" --pretty Output formatted JSON (default if terminal)");
|
|
191
|
+
console.log("\nExamples:");
|
|
192
|
+
console.log(" arc describe ./src/resources.js");
|
|
193
|
+
console.log(" arc describe ./src/resources.js product");
|
|
194
|
+
console.log(" arc describe ./src/resources.js --json | jq .");
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
const entryModule = await import(pathToFileURL(resolve(process.cwd(), entryPath)).href);
|
|
198
|
+
const resources = [];
|
|
199
|
+
function tryCollect(value) {
|
|
200
|
+
if (value && typeof value === "object" && "name" in value && "_registryMeta" in value && "toPlugin" in value) resources.push(value);
|
|
201
|
+
}
|
|
202
|
+
for (const exported of Object.values(entryModule)) if (Array.isArray(exported)) exported.forEach(tryCollect);
|
|
203
|
+
else tryCollect(exported);
|
|
204
|
+
if (resources.length === 0) throw new Error("No resource definitions found in entry file.\nMake sure your file exports defineResource() results:\n export const productResource = defineResource({ ... });");
|
|
205
|
+
const filtered = filterResource ? resources.filter((r) => r.name === filterResource) : resources;
|
|
206
|
+
if (filterResource && filtered.length === 0) throw new Error(`Resource '${filterResource}' not found.\nAvailable: ${resources.map((r) => r.name).join(", ")}`);
|
|
207
|
+
const described = filtered.map((r) => describeResource(r, r._registryMeta?.module));
|
|
208
|
+
const presetCounts = {};
|
|
209
|
+
let totalPipelineSteps = 0;
|
|
210
|
+
let totalFields = 0;
|
|
211
|
+
for (const res of described) {
|
|
212
|
+
for (const preset of res.presets) presetCounts[preset] = (presetCounts[preset] ?? 0) + 1;
|
|
213
|
+
if (res.pipeline) totalPipelineSteps += res.pipeline.guards.length + res.pipeline.transforms.length + res.pipeline.interceptors.length;
|
|
214
|
+
if (res.fields) totalFields += Object.keys(res.fields).length;
|
|
215
|
+
}
|
|
216
|
+
const output = {
|
|
217
|
+
$schema: "arc-describe/v1",
|
|
218
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
219
|
+
resources: described,
|
|
220
|
+
stats: {
|
|
221
|
+
totalResources: described.length,
|
|
222
|
+
totalRoutes: described.reduce((sum, r) => sum + r.routes.length, 0),
|
|
223
|
+
totalEvents: described.reduce((sum, r) => sum + r.events.length, 0),
|
|
224
|
+
totalFields,
|
|
225
|
+
presetUsage: presetCounts,
|
|
226
|
+
pipelineSteps: totalPipelineSteps
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
const json = pretty ? JSON.stringify(output, null, 2) : JSON.stringify(output);
|
|
230
|
+
console.log(json);
|
|
231
|
+
} catch (error) {
|
|
232
|
+
if (error instanceof Error) throw error;
|
|
233
|
+
throw new Error(String(error));
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
//#endregion
|
|
238
|
+
export { describe as default, describe };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
//#region src/cli/commands/docs.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Arc CLI - Docs Command
|
|
4
|
+
*
|
|
5
|
+
* Export OpenAPI specification from registered resources.
|
|
6
|
+
* Requires an entry file that exports defineResource() results.
|
|
7
|
+
*/
|
|
8
|
+
declare function exportDocs(args: string[]): Promise<void>;
|
|
9
|
+
declare const _default: {
|
|
10
|
+
exportDocs: typeof exportDocs;
|
|
11
|
+
};
|
|
12
|
+
//#endregion
|
|
13
|
+
export { _default as default, exportDocs };
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { t as ResourceRegistry } from "../../ResourceRegistry-7Ic20ZMw.mjs";
|
|
2
|
+
import { t as buildOpenApiSpec } from "../../openapi-9nB_kiuR.mjs";
|
|
3
|
+
import { writeFileSync } from "node:fs";
|
|
4
|
+
import { resolve } from "node:path";
|
|
5
|
+
import { pathToFileURL } from "node:url";
|
|
6
|
+
|
|
7
|
+
//#region src/cli/commands/docs.ts
|
|
8
|
+
/**
|
|
9
|
+
* Arc CLI - Docs Command
|
|
10
|
+
*
|
|
11
|
+
* Export OpenAPI specification from registered resources.
|
|
12
|
+
* Requires an entry file that exports defineResource() results.
|
|
13
|
+
*/
|
|
14
|
+
function parseDocsArgs(args) {
|
|
15
|
+
const outputPath = args.find((a) => a.endsWith(".json")) ?? "./openapi.json";
|
|
16
|
+
return {
|
|
17
|
+
entryPath: args.find((a) => !a.endsWith(".json")),
|
|
18
|
+
outputPath
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
async function exportDocs(args) {
|
|
22
|
+
const { entryPath, outputPath } = parseDocsArgs(args);
|
|
23
|
+
console.log("Exporting OpenAPI specification...\n");
|
|
24
|
+
if (!entryPath) throw new Error("Missing entry file.\n\nUsage: arc docs <entry-file> [output.json]\nExample: arc docs ./src/resources.js ./openapi.json");
|
|
25
|
+
const entryModule = await import(pathToFileURL(resolve(process.cwd(), entryPath)).href);
|
|
26
|
+
const registry = new ResourceRegistry();
|
|
27
|
+
let registered = 0;
|
|
28
|
+
function tryRegister(value) {
|
|
29
|
+
if (value && typeof value === "object" && "name" in value && "_registryMeta" in value && "toPlugin" in value) {
|
|
30
|
+
registry.register(value, value._registryMeta ?? {});
|
|
31
|
+
registered++;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
for (const exported of Object.values(entryModule)) if (Array.isArray(exported)) exported.forEach(tryRegister);
|
|
35
|
+
else tryRegister(exported);
|
|
36
|
+
if (registered === 0) throw new Error("No resource definitions found in entry file.\nMake sure your file exports defineResource() results:\n export const productResource = defineResource({ ... });");
|
|
37
|
+
const resources = registry.getAll();
|
|
38
|
+
const spec = buildOpenApiSpec(resources, {
|
|
39
|
+
title: "Arc API",
|
|
40
|
+
version: "1.0.0",
|
|
41
|
+
description: "Auto-generated from Arc resources"
|
|
42
|
+
});
|
|
43
|
+
const fullPath = resolve(process.cwd(), outputPath);
|
|
44
|
+
writeFileSync(fullPath, JSON.stringify(spec, null, 2));
|
|
45
|
+
console.log(`OpenAPI spec exported to: ${fullPath}`);
|
|
46
|
+
console.log(`\nResources included: ${resources.length}`);
|
|
47
|
+
console.log(`Total endpoints: ${Object.keys(spec.paths).length}`);
|
|
48
|
+
}
|
|
49
|
+
var docs_default = { exportDocs };
|
|
50
|
+
|
|
51
|
+
//#endregion
|
|
52
|
+
export { docs_default as default, exportDocs };
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
//#region src/cli/commands/generate.d.ts
|
|
1
2
|
/**
|
|
2
3
|
* Arc CLI - Generate Command
|
|
3
4
|
*
|
|
@@ -12,5 +13,5 @@
|
|
|
12
13
|
* Generate command handler
|
|
13
14
|
*/
|
|
14
15
|
declare function generate(type: string | undefined, args: string[]): Promise<void>;
|
|
15
|
-
|
|
16
|
-
export { generate as default, generate };
|
|
16
|
+
//#endregion
|
|
17
|
+
export { generate as default, generate };
|
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
import { t as pluralize } from "../../pluralize-CM-jZg7p.mjs";
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
|
|
5
|
+
//#region src/cli/commands/generate.ts
|
|
6
|
+
/**
|
|
7
|
+
* Arc CLI - Generate Command
|
|
8
|
+
*
|
|
9
|
+
* Scaffolds resources with consistent naming:
|
|
10
|
+
* - src/resources/product/product.model.ts
|
|
11
|
+
* - src/resources/product/product.repository.ts
|
|
12
|
+
* - src/resources/product/product.resource.ts
|
|
13
|
+
* - src/resources/product/product.controller.ts
|
|
14
|
+
* - src/resources/product/product.schemas.ts
|
|
15
|
+
*/
|
|
16
|
+
function readProjectConfig() {
|
|
17
|
+
try {
|
|
18
|
+
const rcPath = join(process.cwd(), ".arcrc");
|
|
19
|
+
return JSON.parse(readFileSync(rcPath, "utf-8"));
|
|
20
|
+
} catch {
|
|
21
|
+
return {};
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
function isTypeScriptProject() {
|
|
25
|
+
return existsSync(join(process.cwd(), "tsconfig.json"));
|
|
26
|
+
}
|
|
27
|
+
function getTemplates(ts, config = {}) {
|
|
28
|
+
const isMultiTenant = config.tenant === "multi";
|
|
29
|
+
return {
|
|
30
|
+
model: (name) => `/**
|
|
31
|
+
* ${name} Model
|
|
32
|
+
* Generated by Arc CLI
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
import mongoose${ts ? ", { type HydratedDocument }" : ""} from 'mongoose';
|
|
36
|
+
|
|
37
|
+
const { Schema } = mongoose;
|
|
38
|
+
${ts ? `
|
|
39
|
+
export interface I${name} {
|
|
40
|
+
name: string;
|
|
41
|
+
description?: string;
|
|
42
|
+
isActive: boolean;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export type ${name}Document = HydratedDocument<I${name}>;
|
|
46
|
+
` : ""}
|
|
47
|
+
const ${name.toLowerCase()}Schema = new Schema${ts ? `<I${name}>` : ""}(
|
|
48
|
+
{
|
|
49
|
+
name: { type: String, required: true, trim: true },
|
|
50
|
+
description: { type: String, trim: true },
|
|
51
|
+
isActive: { type: Boolean, default: true },
|
|
52
|
+
},
|
|
53
|
+
{ timestamps: true }
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
// Indexes
|
|
57
|
+
${name.toLowerCase()}Schema.index({ name: 1 });
|
|
58
|
+
${name.toLowerCase()}Schema.index({ isActive: 1 });
|
|
59
|
+
|
|
60
|
+
const ${name} = mongoose.models.${name} || mongoose.model('${name}', ${name.toLowerCase()}Schema);
|
|
61
|
+
export default ${name};
|
|
62
|
+
`,
|
|
63
|
+
repository: (name) => `/**
|
|
64
|
+
* ${name} Repository
|
|
65
|
+
* Generated by Arc CLI
|
|
66
|
+
*/
|
|
67
|
+
|
|
68
|
+
import {
|
|
69
|
+
Repository,
|
|
70
|
+
methodRegistryPlugin,
|
|
71
|
+
softDeletePlugin,
|
|
72
|
+
mongoOperationsPlugin,
|
|
73
|
+
} from '@classytic/mongokit';
|
|
74
|
+
import ${name} from './${name.toLowerCase()}.model.js';
|
|
75
|
+
|
|
76
|
+
class ${name}Repository extends Repository {
|
|
77
|
+
constructor() {
|
|
78
|
+
super(${name}, [
|
|
79
|
+
methodRegistryPlugin(),
|
|
80
|
+
softDeletePlugin(),
|
|
81
|
+
mongoOperationsPlugin(),
|
|
82
|
+
]);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Find all active records
|
|
87
|
+
*/
|
|
88
|
+
async findActive() {
|
|
89
|
+
return this.Model.find({ isActive: true, deletedAt: null }).lean();
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const ${name.toLowerCase()}Repository = new ${name}Repository();
|
|
94
|
+
export default ${name.toLowerCase()}Repository;
|
|
95
|
+
export { ${name}Repository };
|
|
96
|
+
`,
|
|
97
|
+
controller: (name) => `/**
|
|
98
|
+
* ${name} Controller
|
|
99
|
+
* Generated by Arc CLI
|
|
100
|
+
*
|
|
101
|
+
* Note: defineResource() auto-creates a controller from the adapter.
|
|
102
|
+
* Only create a custom controller when you need custom methods.
|
|
103
|
+
*/
|
|
104
|
+
|
|
105
|
+
import { BaseController } from '@classytic/arc';
|
|
106
|
+
import ${name.toLowerCase()}Repository from './${name.toLowerCase()}.repository.js';
|
|
107
|
+
|
|
108
|
+
class ${name}Controller extends BaseController {
|
|
109
|
+
constructor() {
|
|
110
|
+
super(${name.toLowerCase()}Repository, {
|
|
111
|
+
resourceName: '${name.toLowerCase()}',
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Add custom controller methods here
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const ${name.toLowerCase()}Controller = new ${name}Controller();
|
|
119
|
+
export default ${name.toLowerCase()}Controller;
|
|
120
|
+
`,
|
|
121
|
+
schemas: (name) => `/**
|
|
122
|
+
* ${name} Schemas
|
|
123
|
+
* Generated by Arc CLI
|
|
124
|
+
*/
|
|
125
|
+
|
|
126
|
+
import ${name} from './${name.toLowerCase()}.model.js';
|
|
127
|
+
import { buildCrudSchemasFromModel } from '@classytic/mongokit/utils';
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* CRUD Schemas with Field Rules
|
|
131
|
+
*/
|
|
132
|
+
const crudSchemas = buildCrudSchemasFromModel(${name}, {
|
|
133
|
+
strictAdditionalProperties: true,
|
|
134
|
+
fieldRules: {
|
|
135
|
+
// Mark fields as system-managed (excluded from create/update)
|
|
136
|
+
// deletedAt: { systemManaged: true },
|
|
137
|
+
},
|
|
138
|
+
query: {
|
|
139
|
+
filterableFields: {
|
|
140
|
+
isActive: 'boolean',
|
|
141
|
+
createdAt: 'date',
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
export default crudSchemas;
|
|
147
|
+
`,
|
|
148
|
+
resource: (name) => {
|
|
149
|
+
const useMongoKit = config.adapter === "mongokit" || !config.adapter;
|
|
150
|
+
const queryParserImport = useMongoKit ? `\nimport { QueryParser } from '@classytic/mongokit';\n\nconst queryParser = new QueryParser();\n` : "";
|
|
151
|
+
const queryParserConfig = useMongoKit ? `\n queryParser,` : "";
|
|
152
|
+
return isMultiTenant ? `/**
|
|
153
|
+
* ${name} Resource
|
|
154
|
+
* Generated by Arc CLI
|
|
155
|
+
*/
|
|
156
|
+
|
|
157
|
+
import { defineResource, createMongooseAdapter } from '@classytic/arc';
|
|
158
|
+
import { requireOrgMembership, requireOrgRole } from '@classytic/arc/permissions';
|
|
159
|
+
import ${name}${ts ? `, { type I${name} }` : ""} from './${name.toLowerCase()}.model.js';
|
|
160
|
+
import ${name.toLowerCase()}Repository from './${name.toLowerCase()}.repository.js';${queryParserImport}
|
|
161
|
+
|
|
162
|
+
const ${name.toLowerCase()}Resource = defineResource${ts ? `<I${name}>` : ""}({
|
|
163
|
+
name: '${name.toLowerCase()}',
|
|
164
|
+
adapter: createMongooseAdapter(${name}, ${name.toLowerCase()}Repository),${queryParserConfig}
|
|
165
|
+
presets: ['softDelete'],
|
|
166
|
+
permissions: {
|
|
167
|
+
list: requireOrgMembership(),
|
|
168
|
+
get: requireOrgMembership(),
|
|
169
|
+
create: requireOrgRole('admin'),
|
|
170
|
+
update: requireOrgRole('admin'),
|
|
171
|
+
delete: requireOrgRole('admin'),
|
|
172
|
+
},
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
export default ${name.toLowerCase()}Resource;
|
|
176
|
+
` : `/**
|
|
177
|
+
* ${name} Resource
|
|
178
|
+
* Generated by Arc CLI
|
|
179
|
+
*/
|
|
180
|
+
|
|
181
|
+
import { defineResource, createMongooseAdapter } from '@classytic/arc';
|
|
182
|
+
import { requireAuth, requireRoles } from '@classytic/arc/permissions';
|
|
183
|
+
import ${name}${ts ? `, { type I${name} }` : ""} from './${name.toLowerCase()}.model.js';
|
|
184
|
+
import ${name.toLowerCase()}Repository from './${name.toLowerCase()}.repository.js';${queryParserImport}
|
|
185
|
+
|
|
186
|
+
const ${name.toLowerCase()}Resource = defineResource${ts ? `<I${name}>` : ""}({
|
|
187
|
+
name: '${name.toLowerCase()}',
|
|
188
|
+
adapter: createMongooseAdapter(${name}, ${name.toLowerCase()}Repository),${queryParserConfig}
|
|
189
|
+
presets: ['softDelete'],
|
|
190
|
+
permissions: {
|
|
191
|
+
list: requireAuth(),
|
|
192
|
+
get: requireAuth(),
|
|
193
|
+
create: requireRoles(['admin']),
|
|
194
|
+
update: requireRoles(['admin']),
|
|
195
|
+
delete: requireRoles(['admin']),
|
|
196
|
+
},
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
export default ${name.toLowerCase()}Resource;
|
|
200
|
+
`;
|
|
201
|
+
},
|
|
202
|
+
test: (name) => `/**
|
|
203
|
+
* ${name} Tests
|
|
204
|
+
* Generated by Arc CLI
|
|
205
|
+
*/
|
|
206
|
+
|
|
207
|
+
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
|
208
|
+
import mongoose from 'mongoose';
|
|
209
|
+
import { createMinimalTestApp } from '@classytic/arc/testing';
|
|
210
|
+
${ts ? "import type { FastifyInstance } from 'fastify';\n" : ""}import ${name.toLowerCase()}Resource from '../src/resources/${name.toLowerCase()}/${name.toLowerCase()}.resource.js';
|
|
211
|
+
|
|
212
|
+
describe('${name} Resource', () => {
|
|
213
|
+
let app${ts ? ": FastifyInstance" : ""};
|
|
214
|
+
|
|
215
|
+
beforeAll(async () => {
|
|
216
|
+
const testDbUri = process.env.MONGODB_URI || 'mongodb://localhost:27017/arc-app-test';
|
|
217
|
+
await mongoose.connect(testDbUri);
|
|
218
|
+
|
|
219
|
+
app = createMinimalTestApp();
|
|
220
|
+
await app.register(${name.toLowerCase()}Resource.toPlugin());
|
|
221
|
+
await app.ready();
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
afterAll(async () => {
|
|
225
|
+
await app.close();
|
|
226
|
+
await mongoose.connection.close();
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
describe('GET /${pluralize(name.toLowerCase())}', () => {
|
|
230
|
+
it('should return a list', async () => {
|
|
231
|
+
const response = await app.inject({
|
|
232
|
+
method: 'GET',
|
|
233
|
+
url: '/${pluralize(name.toLowerCase())}',
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
expect(response.statusCode).toBe(200);
|
|
237
|
+
const body = JSON.parse(response.body);
|
|
238
|
+
expect(body).toHaveProperty('docs');
|
|
239
|
+
});
|
|
240
|
+
});
|
|
241
|
+
});
|
|
242
|
+
`
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Generate command handler
|
|
247
|
+
*/
|
|
248
|
+
async function generate(type, args) {
|
|
249
|
+
if (!type) throw new Error("Missing type argument\nUsage: arc generate <resource|controller|model|repository|schemas> <name>");
|
|
250
|
+
const [name] = args;
|
|
251
|
+
if (!name) throw new Error("Missing name argument\nUsage: arc generate <type> <name>\nExample: arc generate resource product");
|
|
252
|
+
const capitalizedName = name.charAt(0).toUpperCase() + name.slice(1);
|
|
253
|
+
const lowerName = name.toLowerCase();
|
|
254
|
+
const projectConfig = readProjectConfig();
|
|
255
|
+
const ts = projectConfig.typescript ?? isTypeScriptProject();
|
|
256
|
+
const ext = ts ? "ts" : "js";
|
|
257
|
+
const templates = getTemplates(ts, projectConfig);
|
|
258
|
+
const resourcePath = join(process.cwd(), "src", "resources", lowerName);
|
|
259
|
+
switch (type) {
|
|
260
|
+
case "resource":
|
|
261
|
+
case "r":
|
|
262
|
+
await generateResource(capitalizedName, lowerName, resourcePath, templates, ext);
|
|
263
|
+
break;
|
|
264
|
+
case "controller":
|
|
265
|
+
case "c":
|
|
266
|
+
await generateFile(capitalizedName, lowerName, resourcePath, "controller", templates.controller, ext);
|
|
267
|
+
break;
|
|
268
|
+
case "model":
|
|
269
|
+
case "m":
|
|
270
|
+
await generateFile(capitalizedName, lowerName, resourcePath, "model", templates.model, ext);
|
|
271
|
+
break;
|
|
272
|
+
case "repository":
|
|
273
|
+
case "repo":
|
|
274
|
+
await generateFile(capitalizedName, lowerName, resourcePath, "repository", templates.repository, ext);
|
|
275
|
+
break;
|
|
276
|
+
case "schemas":
|
|
277
|
+
case "s":
|
|
278
|
+
await generateFile(capitalizedName, lowerName, resourcePath, "schemas", templates.schemas, ext);
|
|
279
|
+
break;
|
|
280
|
+
default: throw new Error(`Unknown type: ${type}\nAvailable types: resource, controller, model, repository, schemas`);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Generate a full resource
|
|
285
|
+
*/
|
|
286
|
+
async function generateResource(name, lowerName, resourcePath, templates, ext) {
|
|
287
|
+
console.log(`\nGenerating resource: ${name}...\n`);
|
|
288
|
+
if (!existsSync(resourcePath)) {
|
|
289
|
+
mkdirSync(resourcePath, { recursive: true });
|
|
290
|
+
console.log(` + Created: src/resources/${lowerName}/`);
|
|
291
|
+
}
|
|
292
|
+
const files = {
|
|
293
|
+
[`${lowerName}.model.${ext}`]: templates.model(name),
|
|
294
|
+
[`${lowerName}.repository.${ext}`]: templates.repository(name),
|
|
295
|
+
[`${lowerName}.resource.${ext}`]: templates.resource(name)
|
|
296
|
+
};
|
|
297
|
+
for (const [filename, content] of Object.entries(files)) {
|
|
298
|
+
const filepath = join(resourcePath, filename);
|
|
299
|
+
if (existsSync(filepath)) console.warn(` ! Skipped: ${filename} (already exists)`);
|
|
300
|
+
else {
|
|
301
|
+
writeFileSync(filepath, content);
|
|
302
|
+
console.log(` + Created: ${filename}`);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
const testsDir = join(process.cwd(), "tests");
|
|
306
|
+
if (!existsSync(testsDir)) mkdirSync(testsDir, { recursive: true });
|
|
307
|
+
const testPath = join(testsDir, `${lowerName}.test.${ext}`);
|
|
308
|
+
if (!existsSync(testPath)) {
|
|
309
|
+
writeFileSync(testPath, templates.test(name));
|
|
310
|
+
console.log(` + Created: tests/${lowerName}.test.${ext}`);
|
|
311
|
+
}
|
|
312
|
+
const isMultiTenant = readProjectConfig().tenant === "multi";
|
|
313
|
+
console.log(`
|
|
314
|
+
╔═══════════════════════════════════════════════════════════════╗
|
|
315
|
+
║ Resource Generated ║
|
|
316
|
+
╚═══════════════════════════════════════════════════════════════╝
|
|
317
|
+
|
|
318
|
+
Next steps:
|
|
319
|
+
|
|
320
|
+
1. Register in src/resources/index.${ext}:
|
|
321
|
+
import ${lowerName}Resource from './${lowerName}/${lowerName}.resource.js';
|
|
322
|
+
|
|
323
|
+
export const resources = [
|
|
324
|
+
// ... existing resources
|
|
325
|
+
${lowerName}Resource,
|
|
326
|
+
];
|
|
327
|
+
|
|
328
|
+
2. Customize the model schema in:
|
|
329
|
+
src/resources/${lowerName}/${lowerName}.model.${ext}
|
|
330
|
+
|
|
331
|
+
3. Adjust permissions in ${lowerName}.resource.${ext}:
|
|
332
|
+
${isMultiTenant ? ` - requireOrgMembership() → any org member
|
|
333
|
+
- requireOrgRole('admin') → specific org roles` : ` - requireAuth() → any authenticated user
|
|
334
|
+
- requireRoles(['admin']) → specific platform roles`}
|
|
335
|
+
|
|
336
|
+
4. Run tests:
|
|
337
|
+
npm test
|
|
338
|
+
`);
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* Generate a single file
|
|
342
|
+
*/
|
|
343
|
+
async function generateFile(name, lowerName, resourcePath, fileType, template, ext) {
|
|
344
|
+
console.log(`\nGenerating ${fileType}: ${name}...\n`);
|
|
345
|
+
if (!existsSync(resourcePath)) {
|
|
346
|
+
mkdirSync(resourcePath, { recursive: true });
|
|
347
|
+
console.log(` + Created: src/resources/${lowerName}/`);
|
|
348
|
+
}
|
|
349
|
+
const filename = `${lowerName}.${fileType}.${ext}`;
|
|
350
|
+
const filepath = join(resourcePath, filename);
|
|
351
|
+
if (existsSync(filepath)) throw new Error(`${filename} already exists. Remove it first or use a different name.`);
|
|
352
|
+
writeFileSync(filepath, template(name));
|
|
353
|
+
console.log(` + Created: ${filename}`);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
//#endregion
|
|
357
|
+
export { generate as default, generate };
|