@ptkl/toolkit 0.7.2 → 0.8.8
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/bin/toolkit.js +3 -2
- package/dist/builder/register.js +6 -6
- package/dist/builder/sdk6/lit.js +52 -0
- package/dist/builder/sdk6/react.js +56 -0
- package/dist/builder/sdk6/vue3.js +52 -0
- package/dist/builder/sdk6/webcomponents.js +41 -0
- package/dist/commands/apiUsers.js +1 -1
- package/dist/commands/apps.js +26 -2
- package/dist/commands/component.js +302 -19
- package/dist/commands/forge.js +284 -38
- package/dist/commands/functions.js +1 -1
- package/dist/commands/generate-types.js +38 -0
- package/dist/commands/index.js +4 -0
- package/dist/commands/profile.js +1 -1
- package/dist/commands/role.js +1 -1
- package/dist/commands/users.js +1 -1
- package/dist/commands/validate-idl.js +115 -0
- package/dist/lib/idlToDts.js +242 -0
- package/dist/lib/util.js +2 -2
- package/package.json +11 -2
package/dist/commands/forge.js
CHANGED
|
@@ -2,7 +2,8 @@ import { Command } from "commander";
|
|
|
2
2
|
import { build, createServer } from 'vite';
|
|
3
3
|
import { c } from 'tar';
|
|
4
4
|
import { Writable } from "stream";
|
|
5
|
-
import { writeFileSync } from "fs";
|
|
5
|
+
import { writeFileSync, readFileSync } from "fs";
|
|
6
|
+
import axios from 'axios';
|
|
6
7
|
import Util from "../lib/util.js";
|
|
7
8
|
import { join } from 'path';
|
|
8
9
|
class ForgeCommand {
|
|
@@ -26,7 +27,19 @@ class ForgeCommand {
|
|
|
26
27
|
.action(this.runDev))
|
|
27
28
|
.addCommand(new Command("list")
|
|
28
29
|
.description("List all available apps")
|
|
29
|
-
.action(this.listApps))
|
|
30
|
+
.action(this.listApps))
|
|
31
|
+
.addCommand(new Command("install")
|
|
32
|
+
.description("Dry-run the app install script locally against the active profile (no actual installation). Runs for both dev and live by default.")
|
|
33
|
+
.requiredOption("-p, --path <path>", "Path to the app directory (where ptkl.config.js is located)")
|
|
34
|
+
.option("--env <env>", "Run for a specific env only (dev or live). Omit to run for both.")
|
|
35
|
+
.option("-b, --bundle", "Build the bundle before running the install script")
|
|
36
|
+
.action((options) => this.install(options)))
|
|
37
|
+
.addCommand(new Command("uninstall")
|
|
38
|
+
.description("Dry-run the app uninstall script locally against the active profile (no actual uninstallation). Runs for both dev and live by default.")
|
|
39
|
+
.requiredOption("-p, --path <path>", "Path to the app directory (where ptkl.config.js is located)")
|
|
40
|
+
.option("--env <env>", "Run for a specific env only (dev or live). Omit to run for both.")
|
|
41
|
+
.option("-b, --bundle", "Build the bundle before running the uninstall script")
|
|
42
|
+
.action((options) => this.uninstall(options)));
|
|
30
43
|
}
|
|
31
44
|
async bundle(options) {
|
|
32
45
|
const { path, upload } = options;
|
|
@@ -36,7 +49,13 @@ class ForgeCommand {
|
|
|
36
49
|
// Change to the app directory
|
|
37
50
|
process.chdir(path);
|
|
38
51
|
const module = await import(`${path}/ptkl.config.js`);
|
|
39
|
-
const { views, name, version, distPath, icon, type, label, permissions, } = module.default ?? {};
|
|
52
|
+
const { views, name, version, distPath, icon, type, label, permissions, install_permissions, runtime_permissions, entitlements, requires, scripts, ssrRenderer, } = module.default ?? {};
|
|
53
|
+
// Validate combined permissions limit
|
|
54
|
+
const rtPerms = runtime_permissions ?? [];
|
|
55
|
+
const ents = entitlements ?? [];
|
|
56
|
+
if (rtPerms.length + ents.length > 100) {
|
|
57
|
+
throw new Error(`Combined runtime_permissions (${rtPerms.length}) and entitlements (${ents.length}) must not exceed 100 entries`);
|
|
58
|
+
}
|
|
40
59
|
// build manifest file
|
|
41
60
|
const manifest = {
|
|
42
61
|
name,
|
|
@@ -45,52 +64,212 @@ class ForgeCommand {
|
|
|
45
64
|
label,
|
|
46
65
|
icon,
|
|
47
66
|
permissions,
|
|
67
|
+
install_permissions: install_permissions ?? [],
|
|
68
|
+
runtime_permissions: runtime_permissions ?? [],
|
|
69
|
+
entitlements: entitlements ?? [],
|
|
70
|
+
requires: requires ?? null,
|
|
71
|
+
scripts: {},
|
|
48
72
|
type: type || 'platform', // default to 'platform' if not specified
|
|
73
|
+
ssrRenderer,
|
|
49
74
|
};
|
|
50
75
|
const client = Util.getClientForProfile();
|
|
51
76
|
// get base url of the platform client
|
|
52
77
|
const baseUrl = client.getPlatformBaseURL();
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
manifest.
|
|
57
|
-
|
|
78
|
+
// Different build approach for public vs platform apps
|
|
79
|
+
if (type === 'public') {
|
|
80
|
+
// Public apps: standard SPA build with index.html
|
|
81
|
+
manifest.icon = icon;
|
|
82
|
+
console.log("Building public app...");
|
|
83
|
+
await build({
|
|
58
84
|
root: path,
|
|
59
|
-
base,
|
|
60
85
|
build: {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
86
|
+
outDir: distPath,
|
|
87
|
+
emptyOutDir: true,
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
console.log("✅ Public app build completed successfully");
|
|
91
|
+
// Build SSR renderer if specified for public apps
|
|
92
|
+
if (ssrRenderer) {
|
|
93
|
+
try {
|
|
94
|
+
console.log('Building SSR renderer...');
|
|
95
|
+
await build({
|
|
96
|
+
root: path,
|
|
97
|
+
build: {
|
|
98
|
+
outDir: distPath,
|
|
99
|
+
emptyOutDir: false,
|
|
100
|
+
target: 'esnext',
|
|
101
|
+
ssr: ssrRenderer, // Mark this as an SSR build with the entry point
|
|
102
|
+
rollupOptions: {
|
|
103
|
+
output: {
|
|
104
|
+
format: 'esm',
|
|
105
|
+
entryFileNames: 'ssr-renderer.js',
|
|
106
|
+
inlineDynamicImports: true,
|
|
107
|
+
},
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
ssr: {
|
|
111
|
+
noExternal: true, // Bundle all dependencies
|
|
112
|
+
target: 'webworker', // Browser-compatible build
|
|
113
|
+
},
|
|
114
|
+
resolve: {
|
|
115
|
+
conditions: ['browser'], // Use browser versions of packages
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
manifest.ssrRenderer = 'ssr-renderer.js';
|
|
119
|
+
console.log('✓ SSR renderer built successfully');
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
console.error('✗ Failed to build SSR renderer:', error.message || error);
|
|
123
|
+
throw new Error('SSR renderer build failed.');
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
console.log("✅ All builds completed successfully");
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
// Platform apps: custom bundle format with views and scripts
|
|
130
|
+
const base = `${baseUrl}/luma/appservice/v1/forge/static/bundle/${name}/${version}/`;
|
|
131
|
+
manifest.icon = `${base}${icon}`;
|
|
132
|
+
const buildViews = Object.keys(views).map(async (view) => {
|
|
133
|
+
manifest.views[view] = `${view}.bundle.js`;
|
|
134
|
+
try {
|
|
135
|
+
const result = await build({
|
|
136
|
+
root: path,
|
|
137
|
+
base,
|
|
138
|
+
build: {
|
|
139
|
+
outDir: distPath,
|
|
140
|
+
emptyOutDir: false,
|
|
141
|
+
rollupOptions: {
|
|
142
|
+
input: views[view],
|
|
143
|
+
output: {
|
|
144
|
+
format: 'esm',
|
|
145
|
+
entryFileNames: `[name].bundle.js`,
|
|
146
|
+
assetFileNames: (assetInfo) => {
|
|
147
|
+
return '[name].[ext]'; // Example: Customize the output file name format
|
|
148
|
+
},
|
|
149
|
+
manualChunks: undefined,
|
|
150
|
+
inlineDynamicImports: true,
|
|
151
|
+
}
|
|
68
152
|
},
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
153
|
+
},
|
|
154
|
+
plugins: [
|
|
155
|
+
{
|
|
156
|
+
name: 'transform-dynamic-imports',
|
|
157
|
+
generateBundle(options, bundle) {
|
|
158
|
+
// Transform after bundling is complete
|
|
159
|
+
for (const fileName in bundle) {
|
|
160
|
+
const chunk = bundle[fileName];
|
|
161
|
+
if (chunk.type === 'chunk' && chunk.code) {
|
|
162
|
+
// Transform dynamic imports in the final bundled code
|
|
163
|
+
chunk.code = chunk.code.replace(/import\(['"`]\.\/([^'"`]+)['"`]\)/g, `dynamicImport('${base}$1')`);
|
|
164
|
+
// Also handle relative paths without ./
|
|
165
|
+
chunk.code = chunk.code.replace(/import\(['"`]([^'"`\/]+\.js)['"`]\)/g, `dynamicImport('${base}$1')`);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
},
|
|
170
|
+
]
|
|
171
|
+
});
|
|
172
|
+
return result;
|
|
173
|
+
}
|
|
174
|
+
catch (error) {
|
|
175
|
+
throw error;
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
const buildScripts = scripts ? Object.keys(scripts).map(async (script) => {
|
|
179
|
+
manifest.scripts[script] = `${script}.script.js`;
|
|
180
|
+
try {
|
|
181
|
+
const result = await build({
|
|
182
|
+
root: path,
|
|
183
|
+
base,
|
|
184
|
+
build: {
|
|
185
|
+
outDir: distPath,
|
|
186
|
+
emptyOutDir: false,
|
|
187
|
+
target: 'esnext',
|
|
188
|
+
rollupOptions: {
|
|
189
|
+
input: scripts[script],
|
|
190
|
+
external: ['axios'],
|
|
191
|
+
output: {
|
|
192
|
+
format: 'esm',
|
|
193
|
+
entryFileNames: `[name].script.js`,
|
|
194
|
+
assetFileNames: (assetInfo) => {
|
|
195
|
+
return '[name].[ext]';
|
|
196
|
+
},
|
|
197
|
+
manualChunks: undefined,
|
|
198
|
+
inlineDynamicImports: true,
|
|
199
|
+
}
|
|
200
|
+
},
|
|
201
|
+
},
|
|
202
|
+
plugins: [
|
|
203
|
+
{
|
|
204
|
+
name: 'wrap-script-in-function',
|
|
205
|
+
generateBundle(options, bundle) {
|
|
206
|
+
for (const fileName in bundle) {
|
|
207
|
+
const chunk = bundle[fileName];
|
|
208
|
+
if (chunk.type === 'chunk' && chunk.code) {
|
|
209
|
+
let code = chunk.code;
|
|
210
|
+
// Replace `import X from 'axios'` → `const X = axiosAdapter`
|
|
211
|
+
// ([\w$]+ handles both regular names and $ from minification)
|
|
212
|
+
// \s* instead of \s+ because minifier removes space before the quote
|
|
213
|
+
code = code.replace(/import\s+([\w$]+)\s+from\s*['"]axios['"]\s*;?\n?/g, 'const $1=axiosAdapter;\n');
|
|
214
|
+
// Replace `import { foo, bar as baz } from 'axios'`
|
|
215
|
+
code = code.replace(/import\s*\{([^}]+)\}\s*from\s*['"]axios['"]\s*;?\n?/g, (_, imports) => imports.split(',').map((part) => {
|
|
216
|
+
const [orig, alias] = part.trim().split(/\s+as\s+/).map((s) => s.trim());
|
|
217
|
+
return `const ${alias || orig}=axiosAdapter${orig === 'default' || !alias ? '' : `.${orig}`};\n`;
|
|
218
|
+
}).join(''));
|
|
219
|
+
// Wrap in async function so sandbox (isolate-vm / new Function) gets a callable back
|
|
220
|
+
chunk.code = `return async()=>{try{${code}}catch(err){const errorObj={_error_:true,message:err.message,name:err.name||'Error',stack:err.stack};Object.keys(err).forEach(key=>{errorObj[key]=err[key]});if(err.data)errorObj.data=err.data;if(err.statusCode)errorObj.statusCode=err.statusCode;if(err.response)errorObj.response=err.response;if(err.code)errorObj.code=err.code;return errorObj}}`;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
86
223
|
}
|
|
87
224
|
}
|
|
225
|
+
]
|
|
226
|
+
});
|
|
227
|
+
return result;
|
|
228
|
+
}
|
|
229
|
+
catch (error) {
|
|
230
|
+
throw error;
|
|
231
|
+
}
|
|
232
|
+
}) : [];
|
|
233
|
+
const viewResults = await Promise.allSettled(buildViews);
|
|
234
|
+
const failedViews = viewResults.filter(r => r.status === 'rejected');
|
|
235
|
+
if (failedViews.length > 0) {
|
|
236
|
+
console.error('\n❌ Failed to build views:');
|
|
237
|
+
failedViews.forEach((result, index) => {
|
|
238
|
+
const viewNames = Object.keys(views);
|
|
239
|
+
const failedIndices = viewResults.map((r, i) => r.status === 'rejected' ? i : -1).filter(i => i >= 0);
|
|
240
|
+
const viewName = viewNames[failedIndices[index]];
|
|
241
|
+
const reason = result.reason;
|
|
242
|
+
console.error(`\n View: ${viewName}`);
|
|
243
|
+
console.error(` Input: ${views[viewName]}`);
|
|
244
|
+
console.error(` Error: ${reason?.message || String(reason)}`);
|
|
245
|
+
if (reason?.stack) {
|
|
246
|
+
console.error(`\n${reason.stack}`);
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
throw new Error('View build failed. See errors above.');
|
|
250
|
+
}
|
|
251
|
+
if (scripts && Object.keys(scripts).length > 0) {
|
|
252
|
+
const scriptResults = await Promise.allSettled(buildScripts);
|
|
253
|
+
const failedScripts = scriptResults.filter(r => r.status === 'rejected');
|
|
254
|
+
if (failedScripts.length > 0) {
|
|
255
|
+
console.error('\n❌ Failed to build scripts:');
|
|
256
|
+
failedScripts.forEach((result, index) => {
|
|
257
|
+
const scriptNames = Object.keys(scripts);
|
|
258
|
+
const failedIndices = scriptResults.map((r, i) => r.status === 'rejected' ? i : -1).filter(i => i >= 0);
|
|
259
|
+
const scriptName = scriptNames[failedIndices[index]];
|
|
260
|
+
const reason = result.reason;
|
|
261
|
+
console.error(`\n Script: ${scriptName}`);
|
|
262
|
+
console.error(` Input: ${scripts[scriptName]}`);
|
|
263
|
+
console.error(` Error: ${reason?.message || String(reason)}`);
|
|
264
|
+
if (reason?.stack) {
|
|
265
|
+
console.error(`\n${reason.stack}`);
|
|
88
266
|
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
267
|
+
});
|
|
268
|
+
throw new Error('Script build failed. See errors above.');
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
console.log("✅ All builds completed successfully");
|
|
272
|
+
}
|
|
94
273
|
console.log("Packaging app...");
|
|
95
274
|
// // write manifest file
|
|
96
275
|
const manifestPath = `${distPath}/manifest.json`;
|
|
@@ -106,6 +285,8 @@ class ForgeCommand {
|
|
|
106
285
|
c({ gzip: true, cwd: distPath }, ['.']).pipe(bufferStream).on('finish', () => {
|
|
107
286
|
client.forge().bundleUpload(buffer).then(() => {
|
|
108
287
|
console.log('Bundle uploaded successfully');
|
|
288
|
+
}).catch((error) => {
|
|
289
|
+
console.error('\x1b[31m%s\x1b[0m', `Bundle upload failed: ${error.response?.data?.message || error.message}`);
|
|
109
290
|
});
|
|
110
291
|
});
|
|
111
292
|
}
|
|
@@ -154,5 +335,70 @@ class ForgeCommand {
|
|
|
154
335
|
const { data } = await forge.list();
|
|
155
336
|
console.log(data);
|
|
156
337
|
}
|
|
338
|
+
async install(options) {
|
|
339
|
+
const { path, env, bundle } = options;
|
|
340
|
+
if (bundle)
|
|
341
|
+
await this.bundle({ path, upload: false });
|
|
342
|
+
const envs = env ? [env] : ['dev', 'live'];
|
|
343
|
+
for (const e of envs) {
|
|
344
|
+
await ForgeCommand._runScript(path, 'install', e);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
async uninstall(options) {
|
|
348
|
+
const { path, env, bundle } = options;
|
|
349
|
+
if (bundle)
|
|
350
|
+
await this.bundle({ path, upload: false });
|
|
351
|
+
const envs = env ? [env] : ['dev', 'live'];
|
|
352
|
+
for (const e of envs) {
|
|
353
|
+
await ForgeCommand._runScript(path, 'uninstall', e);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
static async _runScript(appPath, scriptType, env = 'dev') {
|
|
357
|
+
const module = await import(`${appPath}/ptkl.config.js`);
|
|
358
|
+
const { name, distPath, scripts } = module.default ?? {};
|
|
359
|
+
if (!name)
|
|
360
|
+
throw new Error(`Could not read app name from ${appPath}/ptkl.config.js`);
|
|
361
|
+
if (!scripts?.[scriptType]) {
|
|
362
|
+
console.log(`No '${scriptType}' script defined in ptkl.config.js`);
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
const scriptFile = join(distPath, `${scriptType}.script.js`);
|
|
366
|
+
let scriptCode;
|
|
367
|
+
try {
|
|
368
|
+
scriptCode = readFileSync(scriptFile, 'utf-8');
|
|
369
|
+
}
|
|
370
|
+
catch {
|
|
371
|
+
throw new Error(`Script file not found: ${scriptFile}\nRun 'forge bundle' first.`);
|
|
372
|
+
}
|
|
373
|
+
const profile = Util.getCurrentProfile();
|
|
374
|
+
global.window = {
|
|
375
|
+
__ENV_VARIABLES__: {
|
|
376
|
+
API_HOST: profile.host,
|
|
377
|
+
INTEGRATION_API: `${profile.host}/luma/integrations`,
|
|
378
|
+
PROJECT_API_TOKEN: profile.token,
|
|
379
|
+
PROJECT_ENV: env,
|
|
380
|
+
}
|
|
381
|
+
};
|
|
382
|
+
global.axiosAdapter = axios;
|
|
383
|
+
console.log(`Running ${scriptType} script for '${name}' (profile: ${profile.name}, env: ${env})...\n`);
|
|
384
|
+
// The bundle is: `return async()=>{try{ ...code... }catch(err){return {_error_:true,...}}}`
|
|
385
|
+
// new Function(scriptCode) creates a function whose body is that code.
|
|
386
|
+
// Calling it returns the async function, which we then await.
|
|
387
|
+
const scriptFn = new Function(scriptCode)();
|
|
388
|
+
const result = await scriptFn();
|
|
389
|
+
// Cleanup globals
|
|
390
|
+
delete global.window;
|
|
391
|
+
delete global.axiosAdapter;
|
|
392
|
+
if (result && result._error_) {
|
|
393
|
+
console.error(`❌ Script error: ${result.message}`);
|
|
394
|
+
if (result.stack)
|
|
395
|
+
console.error(result.stack);
|
|
396
|
+
process.exit(1);
|
|
397
|
+
}
|
|
398
|
+
console.log(`✅ ${scriptType} script completed`);
|
|
399
|
+
if (result !== undefined && result !== null) {
|
|
400
|
+
console.log('Result:', JSON.stringify(result, null, 2));
|
|
401
|
+
}
|
|
402
|
+
}
|
|
157
403
|
}
|
|
158
404
|
export default new ForgeCommand();
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import util from "../lib/util.js";
|
|
3
|
+
import { Platform as Api } from "@ptkl/sdk/beta";
|
|
4
|
+
import { writeFileSync, mkdirSync } from "node:fs";
|
|
5
|
+
import { dirname, resolve } from "node:path";
|
|
6
|
+
import { idlToModuleAugmentation } from "../lib/idlToDts.js";
|
|
7
|
+
class GenerateTypesCommand {
|
|
8
|
+
register() {
|
|
9
|
+
return new Command('generate-types')
|
|
10
|
+
.description('Fetch IDL definitions from the platform and emit a TypeScript ' +
|
|
11
|
+
'module-augmentation .d.ts file that gives your project typed ' +
|
|
12
|
+
'component models, component functions, and platform functions.')
|
|
13
|
+
.option('--output <path>', 'Path where the .d.ts file will be written', './types/ptkl.d.ts')
|
|
14
|
+
.option('--env <env>', 'Environment to fetch IDL from (uses current profile default when omitted)')
|
|
15
|
+
.action(this.generate);
|
|
16
|
+
}
|
|
17
|
+
async generate(options) {
|
|
18
|
+
const profile = util.getCurrentProfile();
|
|
19
|
+
const client = new Api({ token: profile.token, host: profile.host, env: options.env });
|
|
20
|
+
console.log(`⏳ Fetching IDL…`);
|
|
21
|
+
const { data } = await client.system().idl();
|
|
22
|
+
console.log(`✅ IDL fetched: ${Object.keys(data.components ?? {}).length} component(s), ${Object.keys(data.functions ?? {}).length} platform function(s)`);
|
|
23
|
+
const dts = idlToModuleAugmentation(data);
|
|
24
|
+
const outputPath = resolve(process.cwd(), options.output);
|
|
25
|
+
mkdirSync(dirname(outputPath), { recursive: true });
|
|
26
|
+
writeFileSync(outputPath, dts, 'utf8');
|
|
27
|
+
const componentCount = Object.keys(data.components ?? {}).length;
|
|
28
|
+
const functionCount = Object.keys(data.functions ?? {}).length;
|
|
29
|
+
console.log(`✅ Types generated: ${outputPath}\n` +
|
|
30
|
+
` ${componentCount} component(s) • ${functionCount} platform function(s)`);
|
|
31
|
+
console.log('');
|
|
32
|
+
console.log('Next steps:');
|
|
33
|
+
console.log(' 1. Include the file in your tsconfig.json "include" array, or');
|
|
34
|
+
console.log(` add a triple-slash reference: /// <reference path="${options.output}" />`);
|
|
35
|
+
console.log(' 2. Enjoy typed component models and functions!');
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
export default new GenerateTypesCommand();
|
package/dist/commands/index.js
CHANGED
|
@@ -8,6 +8,8 @@ import apps from "./apps.js";
|
|
|
8
8
|
import role from "./role.js";
|
|
9
9
|
import forge from "./forge.js";
|
|
10
10
|
import component from "./component.js";
|
|
11
|
+
import generateTypes from "./generate-types.js";
|
|
12
|
+
import validateIDL from "./validate-idl.js";
|
|
11
13
|
export const commands = [
|
|
12
14
|
profile.register(),
|
|
13
15
|
users.register(),
|
|
@@ -16,6 +18,8 @@ export const commands = [
|
|
|
16
18
|
role.register(),
|
|
17
19
|
forge.register(),
|
|
18
20
|
component.register(),
|
|
21
|
+
generateTypes.register(),
|
|
22
|
+
validateIDL.register(),
|
|
19
23
|
new Command('init')
|
|
20
24
|
.description("Init protokol toolkit")
|
|
21
25
|
.action(Util.init)
|
package/dist/commands/profile.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
2
|
import util from "../lib/util.js";
|
|
3
3
|
import cli from "../lib/cli.js";
|
|
4
|
-
import { APIUser, User } from "@ptkl/sdk";
|
|
4
|
+
import { APIUser, Users as User } from "@ptkl/sdk";
|
|
5
5
|
import password from '@inquirer/password';
|
|
6
6
|
class ProfileCommand {
|
|
7
7
|
register() {
|
package/dist/commands/role.js
CHANGED
package/dist/commands/users.js
CHANGED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { readFileSync, existsSync } from "node:fs";
|
|
3
|
+
import { resolve } from "node:path";
|
|
4
|
+
import util from "../lib/util.js";
|
|
5
|
+
import { Platform as Api } from "@ptkl/sdk/beta";
|
|
6
|
+
class ValidateIDLCommand {
|
|
7
|
+
register() {
|
|
8
|
+
return new Command('validate-idl')
|
|
9
|
+
.description('Validate a value against a component, extension, or platform-function IDL. ' +
|
|
10
|
+
'Pass either --ref to identify the target by ref, or --idl-file to supply an ' +
|
|
11
|
+
'inline IDL. The value to validate can be given as a JSON string (--value) or ' +
|
|
12
|
+
'read from a file (--value-file).')
|
|
13
|
+
.option('--ref <ref>', 'Compound ref identifying the IDL target.\n' +
|
|
14
|
+
' component:ns::name — base component, default schema\n' +
|
|
15
|
+
' component:ns::name.schema — base component, named schema\n' +
|
|
16
|
+
' extension:ns::name/extName — extension IDL on a component\n' +
|
|
17
|
+
' pfn:functionName — platform function input')
|
|
18
|
+
.option('--idl-file <path>', 'Path to a JSON file containing an inline EntityIDL object. Mutually exclusive with --ref.')
|
|
19
|
+
.option('--field <field>', 'Field key to validate against (required for component/extension refs; ignored for pfn).')
|
|
20
|
+
.option('--value <json>', 'JSON-encoded value to validate. Mutually exclusive with --value-file.')
|
|
21
|
+
.option('--value-file <path>', 'Path to a JSON file whose contents are the value to validate. Mutually exclusive with --value.')
|
|
22
|
+
.option('--env <env>', 'Environment to run validation in (uses current profile default when omitted).')
|
|
23
|
+
.action(this.run);
|
|
24
|
+
}
|
|
25
|
+
async run(options) {
|
|
26
|
+
// ── mutual-exclusion guards ────────────────────────────────────────────
|
|
27
|
+
if (options.ref && options.idlFile) {
|
|
28
|
+
console.error('❌ Provide either --ref or --idl-file, not both.');
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
if (!options.ref && !options.idlFile) {
|
|
32
|
+
console.error('❌ Provide either --ref or --idl-file.');
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
if (options.value !== undefined && options.valueFile) {
|
|
36
|
+
console.error('❌ Provide either --value or --value-file, not both.');
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
if (options.value === undefined && !options.valueFile) {
|
|
40
|
+
console.error('❌ Provide --value or --value-file.');
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
// ── resolve value ──────────────────────────────────────────────────────
|
|
44
|
+
let rawValue;
|
|
45
|
+
if (options.valueFile) {
|
|
46
|
+
const path = resolve(process.cwd(), options.valueFile);
|
|
47
|
+
if (!existsSync(path)) {
|
|
48
|
+
console.error(`❌ Value file not found: ${path}`);
|
|
49
|
+
process.exit(1);
|
|
50
|
+
}
|
|
51
|
+
rawValue = readFileSync(path, 'utf8');
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
rawValue = options.value;
|
|
55
|
+
}
|
|
56
|
+
let value;
|
|
57
|
+
try {
|
|
58
|
+
value = JSON.parse(rawValue);
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
console.error('❌ Could not parse value as JSON.');
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
// ── resolve inline IDL ─────────────────────────────────────────────────
|
|
65
|
+
let inlineIDL;
|
|
66
|
+
if (options.idlFile) {
|
|
67
|
+
const path = resolve(process.cwd(), options.idlFile);
|
|
68
|
+
if (!existsSync(path)) {
|
|
69
|
+
console.error(`❌ IDL file not found: ${path}`);
|
|
70
|
+
process.exit(1);
|
|
71
|
+
}
|
|
72
|
+
try {
|
|
73
|
+
inlineIDL = JSON.parse(readFileSync(path, 'utf8'));
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
console.error('❌ Could not parse IDL file as JSON.');
|
|
77
|
+
process.exit(1);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
const profile = util.getCurrentProfile();
|
|
81
|
+
const client = new Api({ token: profile.token, host: profile.host, env: options.env });
|
|
82
|
+
const target = options.ref ? `ref: ${options.ref}` : `inline IDL (${options.idlFile})`;
|
|
83
|
+
console.log(`⏳ Validating value against ${target}…`);
|
|
84
|
+
let result;
|
|
85
|
+
try {
|
|
86
|
+
const req = { value };
|
|
87
|
+
if (options.ref)
|
|
88
|
+
req.ref = options.ref;
|
|
89
|
+
if (inlineIDL)
|
|
90
|
+
req.idl = inlineIDL;
|
|
91
|
+
if (options.field)
|
|
92
|
+
req.field = options.field;
|
|
93
|
+
const { data } = await client.system().idlValidate(req);
|
|
94
|
+
result = data;
|
|
95
|
+
}
|
|
96
|
+
catch (err) {
|
|
97
|
+
const msg = err?.response?.data?.message ?? err?.message ?? String(err);
|
|
98
|
+
console.error(`❌ Request failed: ${msg}`);
|
|
99
|
+
process.exit(1);
|
|
100
|
+
}
|
|
101
|
+
if (result.valid) {
|
|
102
|
+
console.log('✅ Valid');
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
console.error('✗ Invalid');
|
|
106
|
+
if (result.errors?.length) {
|
|
107
|
+
for (const e of result.errors) {
|
|
108
|
+
const loc = e.field ? ` (${e.field})` : '';
|
|
109
|
+
console.error(` •${loc} ${e.message}`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
process.exit(1);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
export default new ValidateIDLCommand();
|