@olenbetong/appframe-vite 6.1.2 → 6.3.0
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 +115 -5
- package/lib/cli-resources-add.d.ts +1 -0
- package/lib/cli-resources-add.js +192 -0
- package/lib/cli-resources-edit.d.ts +1 -0
- package/lib/cli-resources-edit.js +185 -0
- package/lib/cli-resources-generate.d.ts +1 -0
- package/lib/cli-resources-generate.js +43 -0
- package/lib/cli.js +45 -15
- package/lib/devtoolsServer.d.ts +7 -0
- package/lib/devtoolsServer.js +240 -0
- package/lib/index.d.ts +11 -1
- package/lib/index.js +50 -2
- package/lib/resourceGenerate.d.ts +63 -0
- package/lib/resourceGenerate.js +569 -0
- package/lib/resourcesConfig.d.ts +83 -0
- package/lib/resourcesConfig.js +180 -0
- package/package.json +14 -4
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import { createReadStream, existsSync, rmSync } from "node:fs";
|
|
2
|
+
import https from "node:https";
|
|
3
|
+
import { createRequire } from "node:module";
|
|
4
|
+
import { dirname, extname, join } from "node:path";
|
|
5
|
+
import bodyParser from "body-parser";
|
|
6
|
+
import { login } from "./proxy.js";
|
|
7
|
+
import { readResourcesConfig, writeResourcesConfig } from "./resourcesConfig.js";
|
|
8
|
+
const require = createRequire(import.meta.url);
|
|
9
|
+
const ALL_SERVERS = ["dev.obet.no", "stage.obet.no", "test.obet.no"];
|
|
10
|
+
const MIME_TYPES = {
|
|
11
|
+
".html": "text/html; charset=utf-8",
|
|
12
|
+
".js": "application/javascript; charset=utf-8",
|
|
13
|
+
".css": "text/css; charset=utf-8",
|
|
14
|
+
".json": "application/json; charset=utf-8",
|
|
15
|
+
".svg": "image/svg+xml",
|
|
16
|
+
".png": "image/png",
|
|
17
|
+
".ico": "image/x-icon",
|
|
18
|
+
".woff2": "font/woff2",
|
|
19
|
+
".woff": "font/woff",
|
|
20
|
+
};
|
|
21
|
+
function getDevtoolsDist() {
|
|
22
|
+
try {
|
|
23
|
+
const pkgPath = require.resolve("@olenbetong/appframe-devtools/package.json");
|
|
24
|
+
return join(dirname(pkgPath), "dist");
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function sendJson(res, status, data) {
|
|
31
|
+
const body = status === 204 ? "" : JSON.stringify(data);
|
|
32
|
+
res.statusCode = status;
|
|
33
|
+
res.setHeader("Content-Type", "application/json; charset=utf-8");
|
|
34
|
+
res.end(body);
|
|
35
|
+
}
|
|
36
|
+
async function addResourceOnServer(hostname, cookies, dbObjectId, name) {
|
|
37
|
+
const cookieStr = Object.entries(cookies)
|
|
38
|
+
.map(([k, v]) => `${k}=${v}`)
|
|
39
|
+
.join("; ");
|
|
40
|
+
// Name is required by the server; fall back to the DBObjectID itself
|
|
41
|
+
const body = JSON.stringify({
|
|
42
|
+
operation: "create",
|
|
43
|
+
resourceName: "API_Resources",
|
|
44
|
+
// fields + excludeFieldNames match the format generateApiDataHandler sends, which the server requires
|
|
45
|
+
fields: ["PrimKey", "Created", "CreatedBy", "Updated", "UpdatedBy", "CUT", "CDL", "DBObjectID", "Name"],
|
|
46
|
+
excludeFieldNames: true,
|
|
47
|
+
DBObjectID: dbObjectId,
|
|
48
|
+
Name: name ?? dbObjectId,
|
|
49
|
+
});
|
|
50
|
+
return new Promise((resolve) => {
|
|
51
|
+
const req = https.request({
|
|
52
|
+
hostname,
|
|
53
|
+
port: 443,
|
|
54
|
+
path: "/api/data",
|
|
55
|
+
method: "POST",
|
|
56
|
+
headers: {
|
|
57
|
+
"Content-Type": "application/json",
|
|
58
|
+
Accept: "application/json",
|
|
59
|
+
Cookie: cookieStr,
|
|
60
|
+
Origin: `https://${hostname}`,
|
|
61
|
+
"Content-Length": Buffer.byteLength(body),
|
|
62
|
+
},
|
|
63
|
+
}, (incoming) => {
|
|
64
|
+
let raw = "";
|
|
65
|
+
incoming.on("data", (chunk) => {
|
|
66
|
+
raw += chunk;
|
|
67
|
+
});
|
|
68
|
+
incoming.on("end", () => {
|
|
69
|
+
try {
|
|
70
|
+
const json = JSON.parse(raw);
|
|
71
|
+
if (json.error) {
|
|
72
|
+
resolve({ hostname, success: false, error: json.error });
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
resolve({ hostname, success: true });
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
const ok = (incoming.statusCode ?? 0) < 400;
|
|
80
|
+
resolve({ hostname, success: ok, error: ok ? undefined : raw.slice(0, 200) });
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
req.on("error", (err) => resolve({ hostname, success: false, error: err.message }));
|
|
85
|
+
req.write(body);
|
|
86
|
+
req.end();
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
async function handleApi(req, res, apiPath, hostname, next) {
|
|
90
|
+
// apiPath is the path after /__appframe_devtools__/api
|
|
91
|
+
// e.g. /config, /resources or /resources/dsMyObject
|
|
92
|
+
const segments = apiPath
|
|
93
|
+
.replace(/^\/resources/, "")
|
|
94
|
+
.split("/")
|
|
95
|
+
.filter(Boolean);
|
|
96
|
+
const method = req.method?.toUpperCase() ?? "GET";
|
|
97
|
+
if (apiPath === "/config" && method === "GET") {
|
|
98
|
+
return sendJson(res, 200, { hostname });
|
|
99
|
+
}
|
|
100
|
+
// Only handle /resources and /resources/:id
|
|
101
|
+
if (!apiPath.startsWith("/resources") && apiPath !== "/catalog") {
|
|
102
|
+
return next();
|
|
103
|
+
}
|
|
104
|
+
const id = segments[0]; // undefined for collection-level routes
|
|
105
|
+
try {
|
|
106
|
+
if (apiPath === "/catalog" && method === "POST") {
|
|
107
|
+
const { dbObjectId, name } = req.body;
|
|
108
|
+
if (!dbObjectId) {
|
|
109
|
+
return sendJson(res, 400, { error: "dbObjectId is required" });
|
|
110
|
+
}
|
|
111
|
+
const username = process.env.APPFRAME_LOGIN ?? "";
|
|
112
|
+
const password = process.env.APPFRAME_PWD ?? "";
|
|
113
|
+
if (!username || !password) {
|
|
114
|
+
return sendJson(res, 500, { error: "APPFRAME_LOGIN and APPFRAME_PWD environment variables must be set" });
|
|
115
|
+
}
|
|
116
|
+
const settled = await Promise.allSettled(ALL_SERVERS.map(async (server) => {
|
|
117
|
+
const cookies = await login(server, username, password, { silent: true });
|
|
118
|
+
return addResourceOnServer(server, cookies, dbObjectId, name);
|
|
119
|
+
}));
|
|
120
|
+
const results = settled.map((r, i) => r.status === "fulfilled"
|
|
121
|
+
? r.value
|
|
122
|
+
: { hostname: ALL_SERVERS[i], success: false, error: String(r.reason) });
|
|
123
|
+
return sendJson(res, 200, { results });
|
|
124
|
+
}
|
|
125
|
+
if (method === "GET" && !id) {
|
|
126
|
+
const config = await readResourcesConfig();
|
|
127
|
+
return sendJson(res, 200, config);
|
|
128
|
+
}
|
|
129
|
+
if (method === "POST" && !id) {
|
|
130
|
+
const { type, ...entry } = req.body;
|
|
131
|
+
const config = await readResourcesConfig();
|
|
132
|
+
if (type === "procedure") {
|
|
133
|
+
config.procedures = [...(config.procedures ?? []), entry];
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
config.dataObjects = [...(config.dataObjects ?? []), entry];
|
|
137
|
+
}
|
|
138
|
+
await writeResourcesConfig(config);
|
|
139
|
+
return sendJson(res, 201, entry);
|
|
140
|
+
}
|
|
141
|
+
if (method === "PUT" && id) {
|
|
142
|
+
const updates = req.body;
|
|
143
|
+
const config = await readResourcesConfig();
|
|
144
|
+
let found = false;
|
|
145
|
+
for (const list of [config.dataObjects ?? [], config.procedures ?? []]) {
|
|
146
|
+
const idx = list.findIndex((e) => e.id === id);
|
|
147
|
+
if (idx !== -1) {
|
|
148
|
+
list[idx] = { ...list[idx], ...updates };
|
|
149
|
+
found = true;
|
|
150
|
+
break;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
if (!found) {
|
|
154
|
+
return sendJson(res, 404, { error: `Resource '${id}' not found` });
|
|
155
|
+
}
|
|
156
|
+
await writeResourcesConfig(config);
|
|
157
|
+
return sendJson(res, 200, { id, ...updates });
|
|
158
|
+
}
|
|
159
|
+
if (method === "DELETE" && id) {
|
|
160
|
+
const config = await readResourcesConfig();
|
|
161
|
+
let found = false;
|
|
162
|
+
let outputPath;
|
|
163
|
+
for (const key of ["dataObjects", "procedures"]) {
|
|
164
|
+
const list = config[key] ?? [];
|
|
165
|
+
const idx = list.findIndex((e) => e.id === id);
|
|
166
|
+
if (idx !== -1) {
|
|
167
|
+
outputPath = list[idx].output;
|
|
168
|
+
list.splice(idx, 1);
|
|
169
|
+
found = true;
|
|
170
|
+
break;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
if (!found) {
|
|
174
|
+
return sendJson(res, 404, { error: `Resource '${id}' not found` });
|
|
175
|
+
}
|
|
176
|
+
await writeResourcesConfig(config);
|
|
177
|
+
if (outputPath) {
|
|
178
|
+
try {
|
|
179
|
+
rmSync(outputPath, { force: true });
|
|
180
|
+
}
|
|
181
|
+
catch {
|
|
182
|
+
// non-fatal — file may not exist yet
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return sendJson(res, 204, null);
|
|
186
|
+
}
|
|
187
|
+
return next();
|
|
188
|
+
}
|
|
189
|
+
catch (err) {
|
|
190
|
+
return sendJson(res, 500, { error: err instanceof Error ? err.message : String(err) });
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Creates a Connect middleware that:
|
|
195
|
+
* 1. Serves the appframe-devtools static SPA at `/__appframe_devtools__/`
|
|
196
|
+
* 2. Exposes a REST API at `/__appframe_devtools__/api/resources` for resources.yaml CRUD
|
|
197
|
+
*/
|
|
198
|
+
export function createDevtoolsMiddleware(hostname) {
|
|
199
|
+
const jsonParser = bodyParser.json();
|
|
200
|
+
const devtoolsDist = getDevtoolsDist();
|
|
201
|
+
return (req, res, next) => {
|
|
202
|
+
// req.url here is the path after /__appframe_devtools__ (Connect strips the prefix)
|
|
203
|
+
const url = req.url ?? "/";
|
|
204
|
+
// REST API
|
|
205
|
+
if (url.startsWith("/api/")) {
|
|
206
|
+
jsonParser(req, res, () => {
|
|
207
|
+
handleApi(req, res, url.slice(4), hostname, next).catch(next);
|
|
208
|
+
});
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
// Static files
|
|
212
|
+
if (!devtoolsDist) {
|
|
213
|
+
res.statusCode = 503;
|
|
214
|
+
res.setHeader("Content-Type", "text/plain");
|
|
215
|
+
res.end("@olenbetong/appframe-devtools not found or not built.\n" +
|
|
216
|
+
"Run: pnpm --filter @olenbetong/appframe-devtools build");
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
const safePath = url.split("?")[0] || "/";
|
|
220
|
+
const filePath = join(devtoolsDist, safePath === "/" ? "index.html" : safePath);
|
|
221
|
+
if (existsSync(filePath)) {
|
|
222
|
+
const mime = MIME_TYPES[extname(filePath)] ?? "application/octet-stream";
|
|
223
|
+
res.setHeader("Content-Type", mime);
|
|
224
|
+
createReadStream(filePath).pipe(res);
|
|
225
|
+
}
|
|
226
|
+
else {
|
|
227
|
+
// SPA fallback - all unknown paths serve index.html
|
|
228
|
+
const indexPath = join(devtoolsDist, "index.html");
|
|
229
|
+
if (existsSync(indexPath)) {
|
|
230
|
+
res.setHeader("Content-Type", "text/html; charset=utf-8");
|
|
231
|
+
createReadStream(indexPath).pipe(res);
|
|
232
|
+
}
|
|
233
|
+
else {
|
|
234
|
+
res.statusCode = 404;
|
|
235
|
+
res.setHeader("Content-Type", "text/plain");
|
|
236
|
+
res.end("DevTools app not built yet.\n" + "Run: pnpm --filter @olenbetong/appframe-devtools build");
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
};
|
|
240
|
+
}
|
package/lib/index.d.ts
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
import type { Plugin } from "vite";
|
|
2
2
|
import { addAppframeBuildConfig } from "./build.js";
|
|
3
3
|
import { createDevMiddleware } from "./devServer.js";
|
|
4
|
-
export
|
|
4
|
+
export interface AppframePluginOptions {
|
|
5
|
+
/**
|
|
6
|
+
* Whether to automatically generate TypeScript types from the Appframe article
|
|
7
|
+
* when the dev server starts. Set to `false` to skip type generation (e.g. when
|
|
8
|
+
* running Storybook or another tool that doesn't have an article context).
|
|
9
|
+
*
|
|
10
|
+
* @default true
|
|
11
|
+
*/
|
|
12
|
+
generateTypes?: boolean;
|
|
13
|
+
}
|
|
14
|
+
export default function appframe(options?: AppframePluginOptions): Plugin;
|
|
5
15
|
export { addAppframeBuildConfig, createDevMiddleware };
|
package/lib/index.js
CHANGED
|
@@ -1,16 +1,22 @@
|
|
|
1
|
+
import { resolve } from "node:path";
|
|
1
2
|
import bodyParser from "body-parser";
|
|
2
3
|
import { watch } from "chokidar";
|
|
3
4
|
import { addAppframeBuildConfig } from "./build.js";
|
|
5
|
+
import { generateFromConfig } from "./cli-resources-generate.js";
|
|
4
6
|
import { createDevMiddleware, getLoginInfo, getProxyRoutes } from "./devServer.js";
|
|
7
|
+
import { createDevtoolsMiddleware } from "./devtoolsServer.js";
|
|
5
8
|
import { runGenerateTypes } from "./generateTypes.js";
|
|
6
9
|
import { localizeMiddleware } from "./localization.js";
|
|
7
10
|
import { checkSession, getLastSession, login } from "./proxy.js";
|
|
11
|
+
import { RESOURCES_CONFIG_FILE } from "./resourcesConfig.js";
|
|
8
12
|
import { createLogMessage, getServerName } from "./utils.js";
|
|
9
13
|
let command = "build";
|
|
10
14
|
let interval;
|
|
11
15
|
let server;
|
|
12
16
|
let lastHostname;
|
|
13
17
|
let watcher;
|
|
18
|
+
let resourcesWatcher = null;
|
|
19
|
+
let resourcesDebounce = null;
|
|
14
20
|
try {
|
|
15
21
|
let appPkgUrl = `file://${process.cwd()}/package.json`;
|
|
16
22
|
watcher = watch(appPkgUrl).on("all", async () => {
|
|
@@ -26,7 +32,8 @@ catch (error) {
|
|
|
26
32
|
console.log(createLogMessage(`failed to watch package.json: ${error.message}`, { type: "warn" }));
|
|
27
33
|
}
|
|
28
34
|
const jsonParser = bodyParser.json();
|
|
29
|
-
export default function appframe() {
|
|
35
|
+
export default function appframe(options = {}) {
|
|
36
|
+
let { generateTypes = true } = options;
|
|
30
37
|
return {
|
|
31
38
|
name: "appframe",
|
|
32
39
|
resolveId(source) {
|
|
@@ -138,7 +145,33 @@ export default function appframe() {
|
|
|
138
145
|
let { appframe, hostname, username, password } = await getLoginInfo();
|
|
139
146
|
server = _server;
|
|
140
147
|
// Run type generation in the background — doesn't block the dev server from starting.
|
|
141
|
-
|
|
148
|
+
if (generateTypes) {
|
|
149
|
+
runGenerateTypes(hostname, username, password, appframe, _server.config.logger);
|
|
150
|
+
}
|
|
151
|
+
// Watch resources.yaml and regenerate on change
|
|
152
|
+
let resourcesConfigPath = resolve(process.cwd(), RESOURCES_CONFIG_FILE);
|
|
153
|
+
if (resourcesWatcher) {
|
|
154
|
+
await resourcesWatcher.close();
|
|
155
|
+
}
|
|
156
|
+
resourcesWatcher = watch(resourcesConfigPath, { ignoreInitial: true }).on("all", () => {
|
|
157
|
+
if (resourcesDebounce)
|
|
158
|
+
clearTimeout(resourcesDebounce);
|
|
159
|
+
resourcesDebounce = setTimeout(async () => {
|
|
160
|
+
_server.config.logger.info(createLogMessage("resources.yaml changed — regenerating…", { source: hostname }));
|
|
161
|
+
try {
|
|
162
|
+
await generateFromConfig();
|
|
163
|
+
_server.config.logger.info(createLogMessage("resources regenerated", { source: hostname }));
|
|
164
|
+
}
|
|
165
|
+
catch (error) {
|
|
166
|
+
_server.config.logger.error(createLogMessage(`resources regeneration failed: ${error?.message ?? error}`, {
|
|
167
|
+
source: hostname,
|
|
168
|
+
type: "error",
|
|
169
|
+
}));
|
|
170
|
+
}
|
|
171
|
+
}, 300);
|
|
172
|
+
});
|
|
173
|
+
// DevTools panel: serve the devtools app + REST API at /__appframe_devtools__/
|
|
174
|
+
_server.middlewares.use("/__appframe_devtools__", createDevtoolsMiddleware(hostname));
|
|
142
175
|
_server.middlewares.use(`/api/user/localize/new/${appframe.article.id}`, jsonParser);
|
|
143
176
|
_server.middlewares.use(`/api/user/localize/new/${appframe.article.id}`, localizeMiddleware);
|
|
144
177
|
_server.middlewares.use("/data/Logger/LogError", jsonParser);
|
|
@@ -155,6 +188,21 @@ export default function appframe() {
|
|
|
155
188
|
_server.middlewares.use(createDevMiddleware(_server));
|
|
156
189
|
};
|
|
157
190
|
},
|
|
191
|
+
transformIndexHtml(_html, ctx) {
|
|
192
|
+
// Only inject the toolbar in dev mode
|
|
193
|
+
if (command !== "serve")
|
|
194
|
+
return;
|
|
195
|
+
// Only inject for article pages (not the devtools app itself)
|
|
196
|
+
if (ctx.originalUrl?.startsWith("/__appframe_devtools__"))
|
|
197
|
+
return;
|
|
198
|
+
return [
|
|
199
|
+
{
|
|
200
|
+
tag: "script",
|
|
201
|
+
attrs: { src: "/__appframe_devtools__/toolbar.js", defer: true },
|
|
202
|
+
injectTo: "body",
|
|
203
|
+
},
|
|
204
|
+
];
|
|
205
|
+
},
|
|
158
206
|
};
|
|
159
207
|
}
|
|
160
208
|
export { addAppframeBuildConfig, createDevMiddleware };
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { Client } from "@olenbetong/appframe-data";
|
|
2
|
+
/**
|
|
3
|
+
* Format a file with Biome if available. Searches for `biome` in the nearest
|
|
4
|
+
* node_modules/.bin up from cwd, then falls back to the global PATH.
|
|
5
|
+
* Silently skips if Biome is not found or formatting fails.
|
|
6
|
+
*/
|
|
7
|
+
export declare function formatWithBiome(filePath: string): Promise<void>;
|
|
8
|
+
export type CLIOptions = {
|
|
9
|
+
server: string;
|
|
10
|
+
types?: boolean;
|
|
11
|
+
global: boolean;
|
|
12
|
+
id: string;
|
|
13
|
+
fields: string | boolean;
|
|
14
|
+
permissions?: string;
|
|
15
|
+
maxRecords?: string;
|
|
16
|
+
sortOrder?: string;
|
|
17
|
+
master?: string;
|
|
18
|
+
linkFields?: string;
|
|
19
|
+
expose?: string | boolean;
|
|
20
|
+
dynamic: boolean;
|
|
21
|
+
unique?: string;
|
|
22
|
+
overrides?: string;
|
|
23
|
+
distinct?: boolean;
|
|
24
|
+
aggregates?: string;
|
|
25
|
+
groupBy?: string;
|
|
26
|
+
where?: string;
|
|
27
|
+
output?: string;
|
|
28
|
+
typesJsonParamOverrides?: Record<string, string>;
|
|
29
|
+
typesJsonReturnType?: string | null;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Serialize the generation options to a `/* af:config ... *\/` YAML comment block.
|
|
33
|
+
* List-type options (fields, sortOrder, etc.) are emitted as proper YAML lists.
|
|
34
|
+
*/
|
|
35
|
+
export declare function buildYamlConfig(resource: string, options: CLIOptions): string;
|
|
36
|
+
/**
|
|
37
|
+
* Find and parse the `/* af:config ... *\/` block from generated file content.
|
|
38
|
+
* Returns `{ resource, options }` or `null` if no block is found.
|
|
39
|
+
*/
|
|
40
|
+
export declare function parseYamlConfig(fileContent: string): {
|
|
41
|
+
resource: string;
|
|
42
|
+
options: CLIOptions;
|
|
43
|
+
} | null;
|
|
44
|
+
/**
|
|
45
|
+
* Compute the relative import specifier for "custom" from the given output file.
|
|
46
|
+
* Returns a POSIX-style path (e.g. "../custom" or "./custom").
|
|
47
|
+
*/
|
|
48
|
+
export declare function getCustomImportPath(outputPath: string): string;
|
|
49
|
+
export declare function getProcedureDefinition(name: string, procDefinition: any, options: CLIOptions): string;
|
|
50
|
+
export declare function getDataObjectDefinition(name: string, viewDefinition: any, options: CLIOptions): string;
|
|
51
|
+
/**
|
|
52
|
+
* Fetch the resource definition from an authenticated Appframe client.
|
|
53
|
+
*/
|
|
54
|
+
export declare function fetchResourceDefinition(client: Client, resourceName: string): Promise<any>;
|
|
55
|
+
/**
|
|
56
|
+
* Fetch the resource definition from the server, read types.json overrides, and
|
|
57
|
+
* return the generated TypeScript content string (without any header comment).
|
|
58
|
+
*
|
|
59
|
+
* @param resourceName - Pre-resolved resource database object ID (e.g. `aviw_QA_Documents`)
|
|
60
|
+
* @param options - Generation options
|
|
61
|
+
* @param client - Authenticated Appframe client
|
|
62
|
+
*/
|
|
63
|
+
export declare function fetchAndGenerate(resourceName: string, options: CLIOptions, client: Client): Promise<string>;
|