@ptkl/toolkit 0.7.0 → 0.8.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/dist/builder/register.js +6 -6
- package/dist/builder/sdk5/webcomponents.js +38 -0
- 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/apps.js +33 -28
- package/dist/commands/component.js +302 -18
- package/dist/commands/forge.js +204 -36
- package/dist/lib/importMapGenerator.js +96 -0
- package/package.json +10 -1
package/dist/builder/register.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import CompilerSDK4 from "./sdk4/vue2.js";
|
|
2
2
|
import CompilerSDK5 from "./sdk5/vue2.js";
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
const latestVersion =
|
|
6
|
-
const defaultEngine = '
|
|
3
|
+
import CompilerSDK6WebComponents from "./sdk6/webcomponents.js";
|
|
4
|
+
import CompilerSDK6Lit from "./sdk6/lit.js";
|
|
5
|
+
const latestVersion = 6;
|
|
6
|
+
const defaultEngine = 'webcomponents';
|
|
7
7
|
const compilers = {
|
|
8
8
|
'sdk4::vue2': new CompilerSDK4,
|
|
9
9
|
'sdk5::vue2': new CompilerSDK5,
|
|
10
|
-
'
|
|
11
|
-
'
|
|
10
|
+
'sdk6::webcomponents': new CompilerSDK6WebComponents,
|
|
11
|
+
'sdk6::lit': new CompilerSDK6Lit,
|
|
12
12
|
};
|
|
13
13
|
export default {
|
|
14
14
|
getCompiler(sdkVersion, engine) {
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import * as Babel from '@babel/standalone';
|
|
2
|
+
import less from 'less';
|
|
3
|
+
export default class Compiler {
|
|
4
|
+
async compileBabel(expression) {
|
|
5
|
+
let code = Babel.transform(expression, {
|
|
6
|
+
sourceType: "module",
|
|
7
|
+
presets: ["env"]
|
|
8
|
+
});
|
|
9
|
+
return code.code;
|
|
10
|
+
}
|
|
11
|
+
async compileCSS(scope, lang, expression) {
|
|
12
|
+
switch (lang) {
|
|
13
|
+
case 'less':
|
|
14
|
+
if (scope) {
|
|
15
|
+
return await less.render(`.${scope} { ${expression} }`);
|
|
16
|
+
}
|
|
17
|
+
const { css } = await less.render(`${expression}`);
|
|
18
|
+
return css;
|
|
19
|
+
default:
|
|
20
|
+
return expression;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
async compile(ext, content) {
|
|
24
|
+
switch (ext) {
|
|
25
|
+
case 'js':
|
|
26
|
+
return await this.compileBabel(content);
|
|
27
|
+
case 'css':
|
|
28
|
+
return await this.compileCSS(null, "css", content);
|
|
29
|
+
case 'less':
|
|
30
|
+
return await this.compileCSS(null, "less", content);
|
|
31
|
+
default:
|
|
32
|
+
return await Promise.resolve(content);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
getSupportedExt() {
|
|
36
|
+
return ["js", "css", "less", "json", "svg", "html"];
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import * as Babel from '@babel/standalone';
|
|
2
|
+
import * as sass from 'sass';
|
|
3
|
+
export default class Compiler {
|
|
4
|
+
async compileBabel(expression) {
|
|
5
|
+
let code = Babel.transform(expression, {
|
|
6
|
+
sourceType: "module",
|
|
7
|
+
presets: [
|
|
8
|
+
"env",
|
|
9
|
+
["typescript", {
|
|
10
|
+
onlyRemoveTypeImports: true,
|
|
11
|
+
allowDeclareFields: true
|
|
12
|
+
}]
|
|
13
|
+
],
|
|
14
|
+
plugins: [
|
|
15
|
+
["proposal-decorators", { version: "2023-05" }],
|
|
16
|
+
"proposal-class-properties"
|
|
17
|
+
]
|
|
18
|
+
});
|
|
19
|
+
return code.code;
|
|
20
|
+
}
|
|
21
|
+
async compileCSS(scope, lang, expression) {
|
|
22
|
+
switch (lang) {
|
|
23
|
+
case 'scss':
|
|
24
|
+
case 'sass':
|
|
25
|
+
if (scope) {
|
|
26
|
+
const result = sass.compileString(`.${scope} { ${expression} }`);
|
|
27
|
+
return result.css;
|
|
28
|
+
}
|
|
29
|
+
const result = sass.compileString(expression);
|
|
30
|
+
return result.css;
|
|
31
|
+
default:
|
|
32
|
+
return expression;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
async compile(ext, content) {
|
|
36
|
+
switch (ext) {
|
|
37
|
+
case 'js':
|
|
38
|
+
case 'ts':
|
|
39
|
+
return await this.compileBabel(content);
|
|
40
|
+
case 'css':
|
|
41
|
+
return await this.compileCSS(null, "css", content);
|
|
42
|
+
case 'scss':
|
|
43
|
+
case 'sass':
|
|
44
|
+
return await this.compileCSS(null, ext, content);
|
|
45
|
+
default:
|
|
46
|
+
return await Promise.resolve(content);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
getSupportedExt() {
|
|
50
|
+
return ["js", "ts", "css", "scss", "sass", "json", "svg", "html"];
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import * as Babel from '@babel/standalone';
|
|
2
|
+
import * as sass from 'sass';
|
|
3
|
+
export default class Compiler {
|
|
4
|
+
async compileBabel(expression) {
|
|
5
|
+
let code = Babel.transform(expression, {
|
|
6
|
+
sourceType: "module",
|
|
7
|
+
presets: [
|
|
8
|
+
"env",
|
|
9
|
+
["react", { runtime: "automatic" }],
|
|
10
|
+
["typescript", {
|
|
11
|
+
onlyRemoveTypeImports: true,
|
|
12
|
+
isTSX: true,
|
|
13
|
+
allExtensions: true
|
|
14
|
+
}]
|
|
15
|
+
],
|
|
16
|
+
plugins: [
|
|
17
|
+
'@babel/plugin-proposal-class-properties',
|
|
18
|
+
'@babel/plugin-transform-class-static-block'
|
|
19
|
+
]
|
|
20
|
+
});
|
|
21
|
+
return code.code;
|
|
22
|
+
}
|
|
23
|
+
async compileCSS(scope, lang, expression) {
|
|
24
|
+
switch (lang) {
|
|
25
|
+
case 'scss':
|
|
26
|
+
case 'sass':
|
|
27
|
+
if (scope) {
|
|
28
|
+
const result = sass.compileString(`.${scope} { ${expression} }`);
|
|
29
|
+
return result.css;
|
|
30
|
+
}
|
|
31
|
+
const result = sass.compileString(expression);
|
|
32
|
+
return result.css;
|
|
33
|
+
default:
|
|
34
|
+
return expression;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
async compile(ext, content) {
|
|
38
|
+
switch (ext) {
|
|
39
|
+
case 'js':
|
|
40
|
+
case 'jsx':
|
|
41
|
+
case 'ts':
|
|
42
|
+
case 'tsx':
|
|
43
|
+
return await this.compileBabel(content);
|
|
44
|
+
case 'css':
|
|
45
|
+
return await this.compileCSS(null, "css", content);
|
|
46
|
+
case 'scss':
|
|
47
|
+
case 'sass':
|
|
48
|
+
return await this.compileCSS(null, ext, content);
|
|
49
|
+
default:
|
|
50
|
+
return await Promise.resolve(content);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
getSupportedExt() {
|
|
54
|
+
return ["js", "jsx", "ts", "tsx", "css", "scss", "sass", "json", "svg", "html"];
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import * as Babel from '@babel/standalone';
|
|
2
|
+
import * as sass from 'sass';
|
|
3
|
+
export default class Compiler {
|
|
4
|
+
async compileBabel(expression) {
|
|
5
|
+
let code = Babel.transform(expression, {
|
|
6
|
+
sourceType: "module",
|
|
7
|
+
presets: [
|
|
8
|
+
"env",
|
|
9
|
+
["typescript", {
|
|
10
|
+
onlyRemoveTypeImports: true,
|
|
11
|
+
allExtensions: true
|
|
12
|
+
}]
|
|
13
|
+
],
|
|
14
|
+
plugins: [
|
|
15
|
+
'@babel/plugin-proposal-class-properties',
|
|
16
|
+
'@babel/plugin-transform-class-static-block'
|
|
17
|
+
]
|
|
18
|
+
});
|
|
19
|
+
return code.code;
|
|
20
|
+
}
|
|
21
|
+
async compileCSS(scope, lang, expression) {
|
|
22
|
+
switch (lang) {
|
|
23
|
+
case 'scss':
|
|
24
|
+
case 'sass':
|
|
25
|
+
if (scope) {
|
|
26
|
+
const result = sass.compileString(`.${scope} { ${expression} }`);
|
|
27
|
+
return result.css;
|
|
28
|
+
}
|
|
29
|
+
const result = sass.compileString(expression);
|
|
30
|
+
return result.css;
|
|
31
|
+
default:
|
|
32
|
+
return expression;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
async compile(ext, content) {
|
|
36
|
+
switch (ext) {
|
|
37
|
+
case 'js':
|
|
38
|
+
case 'ts':
|
|
39
|
+
return await this.compileBabel(content);
|
|
40
|
+
case 'css':
|
|
41
|
+
return await this.compileCSS(null, "css", content);
|
|
42
|
+
case 'scss':
|
|
43
|
+
case 'sass':
|
|
44
|
+
return await this.compileCSS(null, ext, content);
|
|
45
|
+
default:
|
|
46
|
+
return await Promise.resolve(content);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
getSupportedExt() {
|
|
50
|
+
return ["js", "ts", "css", "scss", "sass", "json", "svg", "html", "vue"];
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import * as Babel from '@babel/standalone';
|
|
2
|
+
import * as sass from 'sass';
|
|
3
|
+
export default class Compiler {
|
|
4
|
+
async compileBabel(expression) {
|
|
5
|
+
let code = Babel.transform(expression, {
|
|
6
|
+
sourceType: "module",
|
|
7
|
+
presets: ["env"]
|
|
8
|
+
});
|
|
9
|
+
return code.code;
|
|
10
|
+
}
|
|
11
|
+
async compileCSS(scope, lang, expression) {
|
|
12
|
+
switch (lang) {
|
|
13
|
+
case 'scss':
|
|
14
|
+
case 'sass':
|
|
15
|
+
if (scope) {
|
|
16
|
+
const result = sass.compileString(`.${scope} { ${expression} }`);
|
|
17
|
+
return result.css;
|
|
18
|
+
}
|
|
19
|
+
const result = sass.compileString(expression);
|
|
20
|
+
return result.css;
|
|
21
|
+
default:
|
|
22
|
+
return expression;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
async compile(ext, content) {
|
|
26
|
+
switch (ext) {
|
|
27
|
+
case 'js':
|
|
28
|
+
return await this.compileBabel(content);
|
|
29
|
+
case 'css':
|
|
30
|
+
return await this.compileCSS(null, "css", content);
|
|
31
|
+
case 'scss':
|
|
32
|
+
case 'sass':
|
|
33
|
+
return await this.compileCSS(null, ext, content);
|
|
34
|
+
default:
|
|
35
|
+
return await Promise.resolve(content);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
getSupportedExt() {
|
|
39
|
+
return ["js", "css", "scss", "sass", "json", "svg", "html"];
|
|
40
|
+
}
|
|
41
|
+
}
|
package/dist/commands/apps.js
CHANGED
|
@@ -76,12 +76,12 @@ class AppsCommand {
|
|
|
76
76
|
async upload(options) {
|
|
77
77
|
const { apptype, directory, build } = options;
|
|
78
78
|
const profile = util.getCurrentProfile();
|
|
79
|
-
const
|
|
79
|
+
const api = new Api({ token: profile.token, host: profile.host }).app(apptype);
|
|
80
80
|
try {
|
|
81
|
-
|
|
81
|
+
let buffer = Buffer.alloc(0);
|
|
82
82
|
const bufferStream = new Writable({
|
|
83
83
|
write(chunk, encoding, callback) {
|
|
84
|
-
|
|
84
|
+
buffer = Buffer.concat([buffer, chunk]);
|
|
85
85
|
callback();
|
|
86
86
|
},
|
|
87
87
|
});
|
|
@@ -93,35 +93,19 @@ class AppsCommand {
|
|
|
93
93
|
.on('error', reject);
|
|
94
94
|
});
|
|
95
95
|
console.log('Archive created successfully');
|
|
96
|
-
//
|
|
97
|
-
const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
|
|
98
|
-
const arrayBuffer = new ArrayBuffer(totalLength);
|
|
99
|
-
const uint8View = new Uint8Array(arrayBuffer);
|
|
100
|
-
let offset = 0;
|
|
101
|
-
for (const chunk of chunks) {
|
|
102
|
-
uint8View.set(new Uint8Array(chunk), offset);
|
|
103
|
-
offset += chunk.length;
|
|
104
|
-
}
|
|
105
|
-
console.log('ArrayBuffer size:', arrayBuffer.byteLength);
|
|
106
|
-
// Create FormData with ArrayBuffer
|
|
96
|
+
// Create FormData with buffer
|
|
107
97
|
const formData = new FormData();
|
|
108
|
-
const blob = new Blob([
|
|
109
|
-
console.log('Blob size:', blob.size);
|
|
98
|
+
const blob = new Blob([buffer], { type: 'application/gzip' });
|
|
110
99
|
formData.append('app', blob, 'app.tar.gz');
|
|
111
100
|
if (build) {
|
|
112
101
|
formData.append('build', 'true');
|
|
113
102
|
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
}
|
|
120
|
-
catch (error) {
|
|
121
|
-
console.log('Upload failed with error:', error.message);
|
|
122
|
-
console.log('Error details:', error);
|
|
123
|
-
throw error;
|
|
124
|
-
}
|
|
103
|
+
return await api.client.post(`/v3/system/gateway/app-service/${apptype}/upload`, formData, {
|
|
104
|
+
timeout: 60000,
|
|
105
|
+
headers: {
|
|
106
|
+
'Content-Type': 'multipart/form-data'
|
|
107
|
+
}
|
|
108
|
+
});
|
|
125
109
|
}
|
|
126
110
|
catch (error) {
|
|
127
111
|
throw error;
|
|
@@ -145,7 +129,7 @@ class AppsCommand {
|
|
|
145
129
|
async bundle(options) {
|
|
146
130
|
const { path, upload } = options;
|
|
147
131
|
const module = await import(`${path}/ptkl.config.js`);
|
|
148
|
-
const { views, name, version, distPath, icon, label, permissions, } = module.default ?? {};
|
|
132
|
+
const { views, name, version, distPath, icon, label, permissions, scripts, } = module.default ?? {};
|
|
149
133
|
// build manifest file
|
|
150
134
|
const manifest = {
|
|
151
135
|
name,
|
|
@@ -154,6 +138,7 @@ class AppsCommand {
|
|
|
154
138
|
label,
|
|
155
139
|
icon,
|
|
156
140
|
permissions,
|
|
141
|
+
scripts,
|
|
157
142
|
};
|
|
158
143
|
const profile = Util.getCurrentProfile();
|
|
159
144
|
const client = Util.getClientForProfile();
|
|
@@ -177,9 +162,29 @@ class AppsCommand {
|
|
|
177
162
|
}
|
|
178
163
|
});
|
|
179
164
|
});
|
|
165
|
+
console.log("manifest", manifest);
|
|
166
|
+
const buildScripts = Object.keys(scripts).map((script) => {
|
|
167
|
+
manifest.scripts[script] = `${script}.script.js`;
|
|
168
|
+
return build({
|
|
169
|
+
root: path,
|
|
170
|
+
base,
|
|
171
|
+
build: {
|
|
172
|
+
rollupOptions: {
|
|
173
|
+
input: scripts[script],
|
|
174
|
+
output: {
|
|
175
|
+
format: 'iife',
|
|
176
|
+
entryFileNames: `${script}.script.js`,
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
});
|
|
180
182
|
await Promise.allSettled(buildViews).catch(err => {
|
|
181
183
|
console.error('Error building:', err);
|
|
182
184
|
});
|
|
185
|
+
await Promise.allSettled(buildScripts).catch(err => {
|
|
186
|
+
console.error('Error building:', err);
|
|
187
|
+
});
|
|
183
188
|
console.log("Packaging app...");
|
|
184
189
|
// // write manifest file
|
|
185
190
|
const manifestPath = `${distPath}/manifest.json`;
|
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
2
|
import Builder from "../builder/index.js";
|
|
3
3
|
import { WebSocketServer } from "ws";
|
|
4
|
-
import { resolve } from "path";
|
|
4
|
+
import { resolve, join, dirname } from "path";
|
|
5
5
|
import { rollup } from "rollup";
|
|
6
6
|
import util from "../lib/util.js";
|
|
7
7
|
import Api from "@ptkl/sdk";
|
|
8
|
+
import { mkdirSync, writeFileSync, rmSync, readdirSync, existsSync, readFileSync } from "fs";
|
|
9
|
+
import { nodeResolve } from '@rollup/plugin-node-resolve';
|
|
10
|
+
// @ts-ignore
|
|
11
|
+
import commonjsPlugin from '@rollup/plugin-commonjs';
|
|
12
|
+
import { babel as babelPlugin } from '@rollup/plugin-babel';
|
|
13
|
+
import { execSync } from 'child_process';
|
|
8
14
|
class ComponentCommand {
|
|
9
15
|
register() {
|
|
10
16
|
return new Command("component")
|
|
@@ -13,37 +19,315 @@ class ComponentCommand {
|
|
|
13
19
|
.addCommand(new Command("builder")
|
|
14
20
|
.description("Run builder for component templates")
|
|
15
21
|
.requiredOption("-v, --version <version>", "SDK version to be used for build", "6")
|
|
16
|
-
.requiredOption("-e, --engine <engine>", "SDK engine to be used for build", "
|
|
17
|
-
.
|
|
22
|
+
.requiredOption("-e, --engine <engine>", "SDK engine to be used for build", "webcomponents")
|
|
23
|
+
.option("-p, --port <port>", "WSS port", "11403")
|
|
24
|
+
.action(this.builder.bind(this)))
|
|
18
25
|
.addCommand(new Command("build")
|
|
19
26
|
.description("Build templates for component")
|
|
20
27
|
.requiredOption("-p, --path <path>", "Path to the source files", "./")
|
|
21
28
|
.action(this.build));
|
|
22
29
|
}
|
|
30
|
+
nodesToActualFiles(nodes, basePath) {
|
|
31
|
+
nodes.forEach((node) => {
|
|
32
|
+
const currentPath = join(basePath, node.name);
|
|
33
|
+
if (node.isLeaf) {
|
|
34
|
+
// Create directory for the file if it doesn't exist
|
|
35
|
+
const dir = dirname(currentPath);
|
|
36
|
+
mkdirSync(dir, { recursive: true });
|
|
37
|
+
// Write the file content
|
|
38
|
+
writeFileSync(currentPath, node.content || '');
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
// Create directory
|
|
42
|
+
mkdirSync(currentPath, { recursive: true });
|
|
43
|
+
// Process children recursively
|
|
44
|
+
if (node.children && node.children.length > 0) {
|
|
45
|
+
this.nodesToActualFiles(node.children, basePath);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
}
|
|
23
50
|
async builder(options) {
|
|
24
|
-
const { version, engine } = options;
|
|
25
|
-
console.log(`Running builder for SDK version ${version} with engine ${engine}`);
|
|
26
|
-
const ws = new WebSocketServer({ port:
|
|
51
|
+
const { version, engine, port } = options;
|
|
52
|
+
console.log(`Running builder for SDK version ${version} with engine ${engine} on port ${port}`);
|
|
53
|
+
const ws = new WebSocketServer({ port: port ?? 11403 });
|
|
27
54
|
ws.on('connection', (socket) => {
|
|
28
55
|
socket.on('message', async (message) => {
|
|
29
|
-
console.log("Building component
|
|
56
|
+
console.log("\n🔨 Building component...");
|
|
30
57
|
const nodes = JSON.parse(message.toString());
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
const compiledFiles = await builder.buildFromNodes(nodes);
|
|
35
|
-
dist = Object.assign({}, compiledFiles);
|
|
36
|
-
socket.send(JSON.stringify({ dist }));
|
|
37
|
-
console.log('Component templates built successfully');
|
|
38
|
-
// Do something with the parsed JSON
|
|
58
|
+
// SDK 6 uses new build system with Rollup and multi-view bundling
|
|
59
|
+
if (parseInt(version) >= 6) {
|
|
60
|
+
await this.buildSDK6(nodes, version, engine, socket);
|
|
39
61
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
62
|
+
else {
|
|
63
|
+
// SDK 5 and below use the legacy build system
|
|
64
|
+
await this.buildLegacy(nodes, version, engine, socket);
|
|
43
65
|
}
|
|
44
66
|
});
|
|
45
67
|
});
|
|
46
68
|
}
|
|
69
|
+
async buildSDK6(nodes, version, engine, socket) {
|
|
70
|
+
// Create temporary directory with constant name
|
|
71
|
+
const tempDir = '/tmp/.ptkl/components';
|
|
72
|
+
try {
|
|
73
|
+
// Parse engine to extract framework and version (e.g., "lit@3" -> { framework: "lit", version: "3" })
|
|
74
|
+
const engineMatch = engine.match(/^([^@]+)(?:@(.+))?$/);
|
|
75
|
+
const framework = engineMatch ? engineMatch[1] : engine;
|
|
76
|
+
const frameworkVersion = engineMatch ? engineMatch[2] : null;
|
|
77
|
+
// Create temp directory
|
|
78
|
+
mkdirSync(tempDir, { recursive: true });
|
|
79
|
+
// Clear all files except node_modules
|
|
80
|
+
const entries = readdirSync(tempDir);
|
|
81
|
+
for (const entry of entries) {
|
|
82
|
+
if (entry !== 'node_modules') {
|
|
83
|
+
const fullPath = join(tempDir, entry);
|
|
84
|
+
rmSync(fullPath, { recursive: true, force: true });
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
// Convert nodes to actual files
|
|
88
|
+
this.nodesToActualFiles(nodes, tempDir);
|
|
89
|
+
// Look for component.js file that defines views
|
|
90
|
+
const componentsConfigPath = join(tempDir, 'component.js');
|
|
91
|
+
let viewsConfig = {};
|
|
92
|
+
if (!existsSync(componentsConfigPath)) {
|
|
93
|
+
throw new Error('component.js not found. Please create a component.js file that exports { views: { viewName: "./path/to/view.js" } }');
|
|
94
|
+
}
|
|
95
|
+
let componentConfig;
|
|
96
|
+
try {
|
|
97
|
+
// Read the component.js file as text
|
|
98
|
+
const componentContent = readFileSync(componentsConfigPath, 'utf-8');
|
|
99
|
+
// Parse the ES module manually
|
|
100
|
+
// Remove 'export default' and evaluate the object
|
|
101
|
+
const contentWithoutExport = componentContent
|
|
102
|
+
.replace(/export\s+default\s+/, '')
|
|
103
|
+
.trim();
|
|
104
|
+
// Use Function constructor to safely evaluate the object
|
|
105
|
+
componentConfig = new Function(`return ${contentWithoutExport}`)();
|
|
106
|
+
if (!componentConfig || !componentConfig.views) {
|
|
107
|
+
throw new Error('component.js must export default object with "views" property');
|
|
108
|
+
}
|
|
109
|
+
viewsConfig = componentConfig.views;
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
throw new Error(`Failed to load component.js: ${error.message}. Please ensure component.js exports { views: { viewName: './path/to/view.js' } }`);
|
|
113
|
+
}
|
|
114
|
+
// Merge user packages with internal dependencies
|
|
115
|
+
const userPackages = componentConfig.packages || {};
|
|
116
|
+
const dependencies = {
|
|
117
|
+
'@ptkl/sdk': '^0.9.12',
|
|
118
|
+
// Lit is bundled into the component, not provided by platform
|
|
119
|
+
...(framework === 'lit' ? {
|
|
120
|
+
'lit': frameworkVersion ? `^${frameworkVersion}` : '^3.0.0'
|
|
121
|
+
} : {}),
|
|
122
|
+
...userPackages
|
|
123
|
+
};
|
|
124
|
+
// Create package.json with merged dependencies
|
|
125
|
+
const packageJson = {
|
|
126
|
+
name: 'component-build',
|
|
127
|
+
version: '1.0.0',
|
|
128
|
+
type: 'module',
|
|
129
|
+
dependencies
|
|
130
|
+
};
|
|
131
|
+
const packageJsonPath = join(tempDir, 'package.json');
|
|
132
|
+
writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2));
|
|
133
|
+
// Run npm install
|
|
134
|
+
try {
|
|
135
|
+
execSync('npm install', {
|
|
136
|
+
cwd: tempDir,
|
|
137
|
+
stdio: 'pipe',
|
|
138
|
+
env: { ...process.env, NODE_ENV: 'production' }
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
catch (error) {
|
|
142
|
+
throw new Error(`npm install failed: ${error.message}`);
|
|
143
|
+
}
|
|
144
|
+
// Build each view separately with Rollup
|
|
145
|
+
const dist = {};
|
|
146
|
+
const warnings = [];
|
|
147
|
+
const viewCount = Object.keys(viewsConfig).length;
|
|
148
|
+
console.log(`📦 Bundling ${viewCount} view${viewCount !== 1 ? 's' : ''}...`);
|
|
149
|
+
for (const [viewName, viewPath] of Object.entries(viewsConfig)) {
|
|
150
|
+
const fullPath = join(tempDir, viewPath);
|
|
151
|
+
// Check and strip @customElement decorator for Lit components
|
|
152
|
+
if (framework === 'lit') {
|
|
153
|
+
const sourceContent = readFileSync(fullPath, 'utf-8');
|
|
154
|
+
const customElementDecoratorRegex = /@customElement\s*\(\s*['"`][^'"`]*['"`]\s*\)/g;
|
|
155
|
+
if (customElementDecoratorRegex.test(sourceContent)) {
|
|
156
|
+
const warning = `${viewName}: Found @customElement decorator which has been automatically removed. The platform handles element registration dynamically.`;
|
|
157
|
+
warnings.push(warning);
|
|
158
|
+
console.warn(`⚠️ ${warning}`);
|
|
159
|
+
// Strip the decorator from source
|
|
160
|
+
const strippedContent = sourceContent.replace(/@customElement\s*\(\s*['"`][^'"`]*['"`]\s*\)/g, '');
|
|
161
|
+
writeFileSync(fullPath, strippedContent, 'utf-8');
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
// Configure Babel based on framework
|
|
165
|
+
let babelConfig = null;
|
|
166
|
+
if (framework === 'lit') {
|
|
167
|
+
babelConfig = {
|
|
168
|
+
babelHelpers: 'bundled',
|
|
169
|
+
extensions: ['.js', '.ts'],
|
|
170
|
+
presets: [
|
|
171
|
+
['@babel/preset-typescript', {
|
|
172
|
+
onlyRemoveTypeImports: true,
|
|
173
|
+
allowDeclareFields: true
|
|
174
|
+
}]
|
|
175
|
+
],
|
|
176
|
+
plugins: [
|
|
177
|
+
['@babel/plugin-proposal-decorators', { legacy: true }],
|
|
178
|
+
'@babel/plugin-proposal-class-properties',
|
|
179
|
+
'@babel/plugin-transform-class-static-block'
|
|
180
|
+
]
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
else if (framework === 'webcomponents') {
|
|
184
|
+
babelConfig = {
|
|
185
|
+
babelHelpers: 'bundled',
|
|
186
|
+
extensions: ['.js', '.ts'],
|
|
187
|
+
presets: [
|
|
188
|
+
['@babel/preset-typescript', {
|
|
189
|
+
onlyRemoveTypeImports: true,
|
|
190
|
+
allowDeclareFields: true
|
|
191
|
+
}]
|
|
192
|
+
],
|
|
193
|
+
plugins: [
|
|
194
|
+
'@babel/plugin-proposal-class-properties',
|
|
195
|
+
'@babel/plugin-transform-class-static-block'
|
|
196
|
+
]
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
// Define external dependencies based on framework
|
|
200
|
+
const getExternalDependencies = (framework) => {
|
|
201
|
+
const externalDeps = [];
|
|
202
|
+
// Lit is bundled into the component, not externalized
|
|
203
|
+
// webcomponents framework has no external dependencies
|
|
204
|
+
return externalDeps;
|
|
205
|
+
};
|
|
206
|
+
const bundle = await rollup({
|
|
207
|
+
input: fullPath,
|
|
208
|
+
plugins: [
|
|
209
|
+
nodeResolve({
|
|
210
|
+
browser: true,
|
|
211
|
+
preferBuiltins: false,
|
|
212
|
+
extensions: ['.js', '.ts', '.jsx', '.tsx', '.vue']
|
|
213
|
+
}),
|
|
214
|
+
// Add Babel plugin if configured
|
|
215
|
+
...(babelConfig ? [babelPlugin(babelConfig)] : []),
|
|
216
|
+
// @ts-ignore
|
|
217
|
+
commonjsPlugin(),
|
|
218
|
+
],
|
|
219
|
+
// Externalize framework dependencies - platform provides them
|
|
220
|
+
external: (id) => {
|
|
221
|
+
const externalDeps = getExternalDependencies(framework);
|
|
222
|
+
return externalDeps.some(dep => {
|
|
223
|
+
if (typeof dep === 'string') {
|
|
224
|
+
return id === dep;
|
|
225
|
+
}
|
|
226
|
+
return dep.test(id);
|
|
227
|
+
});
|
|
228
|
+
},
|
|
229
|
+
onwarn: (warning, warn) => {
|
|
230
|
+
if (warning.code === 'UNRESOLVED_IMPORT') {
|
|
231
|
+
console.warn(`⚠️ Unresolved import in ${viewName}`);
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
warn(warning);
|
|
235
|
+
},
|
|
236
|
+
});
|
|
237
|
+
const { output } = await bundle.generate({
|
|
238
|
+
format: 'esm',
|
|
239
|
+
sourcemap: false,
|
|
240
|
+
inlineDynamicImports: true,
|
|
241
|
+
});
|
|
242
|
+
// Add to dist with view name
|
|
243
|
+
output.forEach((chunk) => {
|
|
244
|
+
if (chunk.type === 'chunk') {
|
|
245
|
+
let code = chunk.code;
|
|
246
|
+
// Check for common issues and add warnings
|
|
247
|
+
// 1. Check if code contains customElements.define and remove it
|
|
248
|
+
// Handle various formats including multiline
|
|
249
|
+
const customElementsDefineRegex = /customElements\.define\s*\([^)]*\([\s\S]*?\)\s*\)\s*;?|customElements\.define\s*\([^)]*\)\s*;?/g;
|
|
250
|
+
const hasCustomElementsDefine = customElementsDefineRegex.test(code);
|
|
251
|
+
if (hasCustomElementsDefine) {
|
|
252
|
+
const warning = `${viewName}: Found customElements.define() which has been automatically removed. The platform handles element registration dynamically based on component context.`;
|
|
253
|
+
warnings.push(warning);
|
|
254
|
+
console.warn(`⚠️ ${warning}`);
|
|
255
|
+
// Remove customElements.define calls (reset regex after test)
|
|
256
|
+
code = code.replace(/customElements\.define\s*\([^)]*\([\s\S]*?\)\s*\)\s*;?/g, '');
|
|
257
|
+
code = code.replace(/customElements\.define\s*\([^)]*\)\s*;?/g, '');
|
|
258
|
+
}
|
|
259
|
+
// 2. Check if code has a default export
|
|
260
|
+
const hasDefaultExport = /export\s+default/.test(code) || /export\s*\{[^}]*default[^}]*\}/.test(code);
|
|
261
|
+
if (!hasDefaultExport) {
|
|
262
|
+
const warning = `${viewName}: Missing default export. The view must export a default component/class for the platform to use.`;
|
|
263
|
+
warnings.push(warning);
|
|
264
|
+
console.warn(`⚠️ ${warning}`);
|
|
265
|
+
}
|
|
266
|
+
// 3. Check if code is suspiciously small (might indicate build issue)
|
|
267
|
+
if (code.length < 50) {
|
|
268
|
+
const warning = `${viewName}: Bundle size is very small (${code.length} bytes). This might indicate a build issue.`;
|
|
269
|
+
warnings.push(warning);
|
|
270
|
+
console.warn(`⚠️ ${warning}`);
|
|
271
|
+
}
|
|
272
|
+
// 4. Check for missing imports that might cause runtime errors
|
|
273
|
+
if (framework === 'lit' && !code.includes('lit')) {
|
|
274
|
+
const warning = `${viewName}: Lit framework not detected in bundle. Make sure you're importing from 'lit'.`;
|
|
275
|
+
warnings.push(warning);
|
|
276
|
+
console.warn(`⚠️ ${warning}`);
|
|
277
|
+
}
|
|
278
|
+
dist[`${viewName}.js`] = code;
|
|
279
|
+
}
|
|
280
|
+
else if (chunk.type === 'asset') {
|
|
281
|
+
dist[`${viewName}-${chunk.fileName}`] = chunk.source.toString();
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
const totalSize = Object.values(dist).reduce((sum, code) => sum + code.length, 0);
|
|
286
|
+
const formattedSize = totalSize > 1024 ? `${(totalSize / 1024).toFixed(2)} KB` : `${totalSize} bytes`;
|
|
287
|
+
console.log(`✅ Build complete: ${Object.keys(dist).length} file${Object.keys(dist).length !== 1 ? 's' : ''} (${formattedSize})`);
|
|
288
|
+
Object.entries(dist).forEach(([name, code]) => {
|
|
289
|
+
const size = code.length > 1024 ? `${(code.length / 1024).toFixed(2)} KB` : `${code.length} bytes`;
|
|
290
|
+
console.log(` • ${name} - ${size}`);
|
|
291
|
+
});
|
|
292
|
+
socket.send(JSON.stringify({
|
|
293
|
+
dist,
|
|
294
|
+
views: Object.keys(viewsConfig),
|
|
295
|
+
warnings: warnings.length > 0 ? warnings : undefined,
|
|
296
|
+
sdk_version: version,
|
|
297
|
+
sdk_engine: engine
|
|
298
|
+
}));
|
|
299
|
+
}
|
|
300
|
+
catch (error) {
|
|
301
|
+
console.error(`❌ Build failed: ${error.message}`);
|
|
302
|
+
socket.send(JSON.stringify({ error: error.message }));
|
|
303
|
+
}
|
|
304
|
+
// Temporary: Keep files for inspection
|
|
305
|
+
// TODO: Uncomment cleanup when ready
|
|
306
|
+
// finally {
|
|
307
|
+
// try {
|
|
308
|
+
// rmSync(tempDir, { recursive: true, force: true });
|
|
309
|
+
// console.log(`Cleaned up temporary directory: ${tempDir}`);
|
|
310
|
+
// } catch (cleanupError) {
|
|
311
|
+
// console.error(`Failed to clean up temporary directory: ${cleanupError}`);
|
|
312
|
+
// }
|
|
313
|
+
// }
|
|
314
|
+
}
|
|
315
|
+
async buildLegacy(nodes, version, engine, socket) {
|
|
316
|
+
try {
|
|
317
|
+
const builder = new Builder(parseInt(version), engine);
|
|
318
|
+
const dist = await builder.buildFromNodes(nodes);
|
|
319
|
+
socket.send(JSON.stringify({
|
|
320
|
+
dist,
|
|
321
|
+
sdk_version: version,
|
|
322
|
+
sdk_engine: engine
|
|
323
|
+
}));
|
|
324
|
+
console.log(`✅ Build complete (SDK ${version})`);
|
|
325
|
+
}
|
|
326
|
+
catch (error) {
|
|
327
|
+
console.error(`❌ Build failed: ${error.message}`);
|
|
328
|
+
socket.send(JSON.stringify({ error: error.message }));
|
|
329
|
+
}
|
|
330
|
+
}
|
|
47
331
|
async build(options) {
|
|
48
332
|
try {
|
|
49
333
|
const { path } = options;
|
package/dist/commands/forge.js
CHANGED
|
@@ -36,7 +36,7 @@ class ForgeCommand {
|
|
|
36
36
|
// Change to the app directory
|
|
37
37
|
process.chdir(path);
|
|
38
38
|
const module = await import(`${path}/ptkl.config.js`);
|
|
39
|
-
const { views, name, version, distPath, icon, type, label, permissions, } = module.default ?? {};
|
|
39
|
+
const { views, name, version, distPath, icon, type, label, permissions, scripts, ssrRenderer, } = module.default ?? {};
|
|
40
40
|
// build manifest file
|
|
41
41
|
const manifest = {
|
|
42
42
|
name,
|
|
@@ -45,52 +45,218 @@ class ForgeCommand {
|
|
|
45
45
|
label,
|
|
46
46
|
icon,
|
|
47
47
|
permissions,
|
|
48
|
+
scripts: {},
|
|
48
49
|
type: type || 'platform', // default to 'platform' if not specified
|
|
50
|
+
ssrRenderer,
|
|
49
51
|
};
|
|
50
52
|
const client = Util.getClientForProfile();
|
|
51
53
|
// get base url of the platform client
|
|
52
54
|
const baseUrl = client.getPlatformBaseURL();
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
manifest.
|
|
57
|
-
|
|
55
|
+
// Different build approach for public vs platform apps
|
|
56
|
+
if (type === 'public') {
|
|
57
|
+
// Public apps: standard SPA build with index.html
|
|
58
|
+
manifest.icon = icon;
|
|
59
|
+
console.log("Building public app...");
|
|
60
|
+
await build({
|
|
58
61
|
root: path,
|
|
59
|
-
base,
|
|
60
62
|
build: {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
63
|
+
outDir: distPath,
|
|
64
|
+
emptyOutDir: true,
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
console.log("✅ Public app build completed successfully");
|
|
68
|
+
// Build SSR renderer if specified for public apps
|
|
69
|
+
if (ssrRenderer) {
|
|
70
|
+
try {
|
|
71
|
+
console.log('Building SSR renderer...');
|
|
72
|
+
await build({
|
|
73
|
+
root: path,
|
|
74
|
+
build: {
|
|
75
|
+
outDir: distPath,
|
|
76
|
+
emptyOutDir: false,
|
|
77
|
+
target: 'esnext',
|
|
78
|
+
ssr: ssrRenderer, // Mark this as an SSR build with the entry point
|
|
79
|
+
rollupOptions: {
|
|
80
|
+
output: {
|
|
81
|
+
format: 'esm',
|
|
82
|
+
entryFileNames: 'ssr-renderer.js',
|
|
83
|
+
inlineDynamicImports: true,
|
|
84
|
+
},
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
ssr: {
|
|
88
|
+
noExternal: true, // Bundle all dependencies
|
|
89
|
+
target: 'webworker', // Browser-compatible build
|
|
90
|
+
},
|
|
91
|
+
resolve: {
|
|
92
|
+
conditions: ['browser'], // Use browser versions of packages
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
manifest.ssrRenderer = 'ssr-renderer.js';
|
|
96
|
+
console.log('✓ SSR renderer built successfully');
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
console.error('✗ Failed to build SSR renderer:', error.message || error);
|
|
100
|
+
throw new Error('SSR renderer build failed.');
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
console.log("✅ All builds completed successfully");
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
// Platform apps: custom bundle format with views and scripts
|
|
107
|
+
const base = `${baseUrl}/luma/appservice/v1/forge/static/bundle/${name}/${version}/`;
|
|
108
|
+
manifest.icon = `${base}${icon}`;
|
|
109
|
+
const buildViews = Object.keys(views).map(async (view) => {
|
|
110
|
+
manifest.views[view] = `${view}.bundle.js`;
|
|
111
|
+
try {
|
|
112
|
+
const result = await build({
|
|
113
|
+
root: path,
|
|
114
|
+
base,
|
|
115
|
+
build: {
|
|
116
|
+
outDir: distPath,
|
|
117
|
+
emptyOutDir: false,
|
|
118
|
+
rollupOptions: {
|
|
119
|
+
input: views[view],
|
|
120
|
+
output: {
|
|
121
|
+
format: 'esm',
|
|
122
|
+
entryFileNames: `[name].bundle.js`,
|
|
123
|
+
assetFileNames: (assetInfo) => {
|
|
124
|
+
return '[name].[ext]'; // Example: Customize the output file name format
|
|
125
|
+
},
|
|
126
|
+
manualChunks: undefined,
|
|
127
|
+
inlineDynamicImports: true,
|
|
128
|
+
}
|
|
68
129
|
},
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
130
|
+
},
|
|
131
|
+
plugins: [
|
|
132
|
+
{
|
|
133
|
+
name: 'transform-dynamic-imports',
|
|
134
|
+
generateBundle(options, bundle) {
|
|
135
|
+
// Transform after bundling is complete
|
|
136
|
+
for (const fileName in bundle) {
|
|
137
|
+
const chunk = bundle[fileName];
|
|
138
|
+
if (chunk.type === 'chunk' && chunk.code) {
|
|
139
|
+
// Transform dynamic imports in the final bundled code
|
|
140
|
+
chunk.code = chunk.code.replace(/import\(['"`]\.\/([^'"`]+)['"`]\)/g, `dynamicImport('${base}$1')`);
|
|
141
|
+
// Also handle relative paths without ./
|
|
142
|
+
chunk.code = chunk.code.replace(/import\(['"`]([^'"`\/]+\.js)['"`]\)/g, `dynamicImport('${base}$1')`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
]
|
|
148
|
+
});
|
|
149
|
+
return result;
|
|
150
|
+
}
|
|
151
|
+
catch (error) {
|
|
152
|
+
throw error;
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
const buildScripts = scripts ? Object.keys(scripts).map(async (script) => {
|
|
156
|
+
manifest.scripts[script] = `${script}.script.js`;
|
|
157
|
+
try {
|
|
158
|
+
const result = await build({
|
|
159
|
+
root: path,
|
|
160
|
+
base,
|
|
161
|
+
build: {
|
|
162
|
+
outDir: distPath,
|
|
163
|
+
emptyOutDir: false,
|
|
164
|
+
target: 'esnext',
|
|
165
|
+
rollupOptions: {
|
|
166
|
+
input: scripts[script],
|
|
167
|
+
external: ['axios'],
|
|
168
|
+
output: {
|
|
169
|
+
format: 'esm',
|
|
170
|
+
entryFileNames: `[name].script.js`,
|
|
171
|
+
assetFileNames: (assetInfo) => {
|
|
172
|
+
return '[name].[ext]'; // Example: Customize the output file name format
|
|
173
|
+
},
|
|
174
|
+
globals: {
|
|
175
|
+
axios: 'axiosAdapter'
|
|
176
|
+
},
|
|
177
|
+
manualChunks: undefined,
|
|
178
|
+
inlineDynamicImports: true,
|
|
179
|
+
}
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
plugins: [
|
|
183
|
+
{
|
|
184
|
+
name: 'wrap-script-in-function',
|
|
185
|
+
generateBundle(options, bundle) {
|
|
186
|
+
for (const fileName in bundle) {
|
|
187
|
+
const chunk = bundle[fileName];
|
|
188
|
+
if (chunk.type === 'chunk' && chunk.code) {
|
|
189
|
+
// Replace import statements with direct variable assignment from globals
|
|
190
|
+
// Handle both regular and minified versions
|
|
191
|
+
let code = chunk.code.replace(/import\s*(\w+)\s*from\s*['"]axios['"]\s*;?/g, 'const $1=axiosAdapter;');
|
|
192
|
+
code = code.replace(/import\s*\{([^}]+)\}\s*from\s*['"]axios['"]\s*;?/g, (match, imports) => {
|
|
193
|
+
// Handle named imports like { default as axios }
|
|
194
|
+
const parts = imports.split(',').map(i => i.trim());
|
|
195
|
+
return parts.map(part => {
|
|
196
|
+
if (part.includes(' as ')) {
|
|
197
|
+
const [original, alias] = part.split(' as ').map(s => s.trim());
|
|
198
|
+
if (original === 'default') {
|
|
199
|
+
return `const ${alias}=axiosAdapter;`;
|
|
200
|
+
}
|
|
201
|
+
return `const ${alias}=axiosAdapter.${original};`;
|
|
202
|
+
}
|
|
203
|
+
return `const ${part}=axiosAdapter.${part};`;
|
|
204
|
+
}).join('');
|
|
205
|
+
});
|
|
206
|
+
// Wrap in async function (minified)
|
|
207
|
+
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}}`;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
86
210
|
}
|
|
87
211
|
}
|
|
212
|
+
]
|
|
213
|
+
});
|
|
214
|
+
return result;
|
|
215
|
+
}
|
|
216
|
+
catch (error) {
|
|
217
|
+
throw error;
|
|
218
|
+
}
|
|
219
|
+
}) : [];
|
|
220
|
+
const viewResults = await Promise.allSettled(buildViews);
|
|
221
|
+
const failedViews = viewResults.filter(r => r.status === 'rejected');
|
|
222
|
+
if (failedViews.length > 0) {
|
|
223
|
+
console.error('\n❌ Failed to build views:');
|
|
224
|
+
failedViews.forEach((result, index) => {
|
|
225
|
+
const viewNames = Object.keys(views);
|
|
226
|
+
const failedIndices = viewResults.map((r, i) => r.status === 'rejected' ? i : -1).filter(i => i >= 0);
|
|
227
|
+
const viewName = viewNames[failedIndices[index]];
|
|
228
|
+
const reason = result.reason;
|
|
229
|
+
console.error(`\n View: ${viewName}`);
|
|
230
|
+
console.error(` Input: ${views[viewName]}`);
|
|
231
|
+
console.error(` Error: ${reason?.message || String(reason)}`);
|
|
232
|
+
if (reason?.stack) {
|
|
233
|
+
console.error(`\n${reason.stack}`);
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
throw new Error('View build failed. See errors above.');
|
|
237
|
+
}
|
|
238
|
+
if (scripts && Object.keys(scripts).length > 0) {
|
|
239
|
+
const scriptResults = await Promise.allSettled(buildScripts);
|
|
240
|
+
const failedScripts = scriptResults.filter(r => r.status === 'rejected');
|
|
241
|
+
if (failedScripts.length > 0) {
|
|
242
|
+
console.error('\n❌ Failed to build scripts:');
|
|
243
|
+
failedScripts.forEach((result, index) => {
|
|
244
|
+
const scriptNames = Object.keys(scripts);
|
|
245
|
+
const failedIndices = scriptResults.map((r, i) => r.status === 'rejected' ? i : -1).filter(i => i >= 0);
|
|
246
|
+
const scriptName = scriptNames[failedIndices[index]];
|
|
247
|
+
const reason = result.reason;
|
|
248
|
+
console.error(`\n Script: ${scriptName}`);
|
|
249
|
+
console.error(` Input: ${scripts[scriptName]}`);
|
|
250
|
+
console.error(` Error: ${reason?.message || String(reason)}`);
|
|
251
|
+
if (reason?.stack) {
|
|
252
|
+
console.error(`\n${reason.stack}`);
|
|
88
253
|
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
254
|
+
});
|
|
255
|
+
throw new Error('Script build failed. See errors above.');
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
console.log("✅ All builds completed successfully");
|
|
259
|
+
}
|
|
94
260
|
console.log("Packaging app...");
|
|
95
261
|
// // write manifest file
|
|
96
262
|
const manifestPath = `${distPath}/manifest.json`;
|
|
@@ -106,6 +272,8 @@ class ForgeCommand {
|
|
|
106
272
|
c({ gzip: true, cwd: distPath }, ['.']).pipe(bufferStream).on('finish', () => {
|
|
107
273
|
client.forge().bundleUpload(buffer).then(() => {
|
|
108
274
|
console.log('Bundle uploaded successfully');
|
|
275
|
+
}).catch((error) => {
|
|
276
|
+
console.error('\x1b[31m%s\x1b[0m', `Bundle upload failed: ${error.response?.data?.message || error.message}`);
|
|
109
277
|
});
|
|
110
278
|
});
|
|
111
279
|
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility to generate import maps for component frameworks
|
|
3
|
+
* This should be used on the frontend to dynamically create import maps
|
|
4
|
+
* based on sdk_version and sdk_engine
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Generate an import map configuration for a given framework
|
|
8
|
+
*
|
|
9
|
+
* @param sdkEngine - The engine string (e.g., "react@18", "lit@3", "vue3")
|
|
10
|
+
* @returns Import map configuration object
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```typescript
|
|
14
|
+
* const importMap = generateImportMap("react@18");
|
|
15
|
+
* // Inject into HTML:
|
|
16
|
+
* const script = document.createElement('script');
|
|
17
|
+
* script.type = 'importmap';
|
|
18
|
+
* script.textContent = JSON.stringify(importMap);
|
|
19
|
+
* document.head.appendChild(script);
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export function generateImportMap(sdkEngine) {
|
|
23
|
+
// Parse engine to extract framework and version (e.g., "lit@3" -> { framework: "lit", version: "3" })
|
|
24
|
+
const engineMatch = sdkEngine.match(/^([^@]+)(?:@(.+))?$/);
|
|
25
|
+
const framework = engineMatch ? engineMatch[1] : sdkEngine;
|
|
26
|
+
const frameworkVersion = engineMatch ? engineMatch[2] : null;
|
|
27
|
+
const imports = {};
|
|
28
|
+
if (framework === 'react') {
|
|
29
|
+
const version = frameworkVersion || '18';
|
|
30
|
+
imports['react'] = `https://esm.sh/react@${version}`;
|
|
31
|
+
imports['react-dom'] = `https://esm.sh/react-dom@${version}`;
|
|
32
|
+
imports['react/jsx-runtime'] = `https://esm.sh/react@${version}/jsx-runtime`;
|
|
33
|
+
imports['react/jsx-dev-runtime'] = `https://esm.sh/react@${version}/jsx-dev-runtime`;
|
|
34
|
+
}
|
|
35
|
+
else if (framework === 'vue3' || framework === 'vue') {
|
|
36
|
+
const version = frameworkVersion || '3';
|
|
37
|
+
imports['vue'] = `https://esm.sh/vue@${version}`;
|
|
38
|
+
}
|
|
39
|
+
else if (framework === 'lit') {
|
|
40
|
+
const version = frameworkVersion || '3';
|
|
41
|
+
imports['lit'] = `https://esm.sh/lit@${version}`;
|
|
42
|
+
imports['lit/decorators.js'] = `https://esm.sh/lit@${version}/decorators.js`;
|
|
43
|
+
imports['lit/directive.js'] = `https://esm.sh/lit@${version}/directive.js`;
|
|
44
|
+
imports['lit/directives/class-map.js'] = `https://esm.sh/lit@${version}/directives/class-map.js`;
|
|
45
|
+
imports['lit/directives/style-map.js'] = `https://esm.sh/lit@${version}/directives/style-map.js`;
|
|
46
|
+
imports['lit/directives/if-defined.js'] = `https://esm.sh/lit@${version}/directives/if-defined.js`;
|
|
47
|
+
imports['lit/directives/repeat.js'] = `https://esm.sh/lit@${version}/directives/repeat.js`;
|
|
48
|
+
imports['lit/directives/unsafe-html.js'] = `https://esm.sh/lit@${version}/directives/unsafe-html.js`;
|
|
49
|
+
}
|
|
50
|
+
else if (framework === 'webcomponents') {
|
|
51
|
+
// For vanilla web components, no external framework needed
|
|
52
|
+
return { imports: {} };
|
|
53
|
+
}
|
|
54
|
+
return { imports };
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Generate the import map HTML script tag as a string
|
|
58
|
+
*
|
|
59
|
+
* @param sdkEngine - The engine string (e.g., "react@18", "lit@3", "vue3")
|
|
60
|
+
* @returns HTML string for the import map script tag
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* ```typescript
|
|
64
|
+
* const scriptTag = generateImportMapHTML("react@18");
|
|
65
|
+
* document.head.insertAdjacentHTML('beforeend', scriptTag);
|
|
66
|
+
* ```
|
|
67
|
+
*/
|
|
68
|
+
export function generateImportMapHTML(sdkEngine) {
|
|
69
|
+
const importMap = generateImportMap(sdkEngine);
|
|
70
|
+
if (Object.keys(importMap.imports).length === 0) {
|
|
71
|
+
return ''; // No import map needed
|
|
72
|
+
}
|
|
73
|
+
return `<script type="importmap">\n${JSON.stringify(importMap, null, 2)}\n</script>`;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Inject import map into the document dynamically
|
|
77
|
+
*
|
|
78
|
+
* @param sdkEngine - The engine string (e.g., "react@18", "lit@3", "vue3")
|
|
79
|
+
*
|
|
80
|
+
* @example
|
|
81
|
+
* ```typescript
|
|
82
|
+
* // In your frontend code:
|
|
83
|
+
* injectImportMap("react@18");
|
|
84
|
+
* // Now you can load component modules
|
|
85
|
+
* ```
|
|
86
|
+
*/
|
|
87
|
+
export function injectImportMap(sdkEngine) {
|
|
88
|
+
const importMap = generateImportMap(sdkEngine);
|
|
89
|
+
if (Object.keys(importMap.imports).length === 0) {
|
|
90
|
+
return; // No import map needed
|
|
91
|
+
}
|
|
92
|
+
const script = document.createElement('script');
|
|
93
|
+
script.type = 'importmap';
|
|
94
|
+
script.textContent = JSON.stringify(importMap, null, 2);
|
|
95
|
+
document.head.appendChild(script);
|
|
96
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ptkl/toolkit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "A command-line toolkit for managing Protokol platform applications, profiles, functions, and components",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"protokol",
|
|
@@ -28,9 +28,17 @@
|
|
|
28
28
|
"prepublishOnly": "npm run build"
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
|
+
"@babel/plugin-proposal-class-properties": "^7.18.6",
|
|
32
|
+
"@babel/plugin-proposal-decorators": "^7.28.0",
|
|
33
|
+
"@babel/plugin-transform-class-static-block": "^7.28.3",
|
|
34
|
+
"@babel/preset-react": "^7.28.5",
|
|
35
|
+
"@babel/preset-typescript": "^7.28.5",
|
|
31
36
|
"@babel/standalone": "^7.26.10",
|
|
32
37
|
"@inquirer/password": "^4.0.21",
|
|
33
38
|
"@ptkl/sdk": "^0.9.12",
|
|
39
|
+
"@rollup/plugin-babel": "^6.1.0",
|
|
40
|
+
"@rollup/plugin-commonjs": "^29.0.0",
|
|
41
|
+
"@rollup/plugin-node-resolve": "^16.0.3",
|
|
34
42
|
"@types/axios": "^0.14.0",
|
|
35
43
|
"@types/commander": "^2.12.2",
|
|
36
44
|
"@types/js-yaml": "^4.0.9",
|
|
@@ -45,6 +53,7 @@
|
|
|
45
53
|
"lodash": "^4.17.21",
|
|
46
54
|
"open": "^10.1.2",
|
|
47
55
|
"rollup": "^4.34.6",
|
|
56
|
+
"sass": "^1.95.1",
|
|
48
57
|
"tar": "^7.4.3",
|
|
49
58
|
"vite": "^6.0.6",
|
|
50
59
|
"vue-template-compiler": "^2.7.16",
|