parapoly-runtime 1.0.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 ADDED
@@ -0,0 +1,50 @@
1
+ # parapoly-runtime
2
+
3
+ ParaPoly 3D CAD Runtime — CSG modeling, sketch, and project builder.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ # 项目内安装
9
+ npm install parapoly-runtime
10
+
11
+ # 全局安装(注册 code3d-build 命令)
12
+ npm install -g parapoly-runtime
13
+ ```
14
+
15
+ ## Usage (Library)
16
+
17
+ ```typescript
18
+ import { buildCode3dProject, Code3dProjectConfig } from 'parapoly-runtime';
19
+ ```
20
+
21
+ ## Usage (CLI)
22
+
23
+ ```bash
24
+ # 全局安装后,在项目目录执行
25
+ code3d-build [workspace-dir] [options]
26
+
27
+ # 或通过 node 直接运行
28
+ node node_modules/parapoly-runtime/cli/code3d-build.js ./my-project
29
+ ```
30
+
31
+ ### CLI Options
32
+
33
+ | Option | Description |
34
+ |--------|-------------|
35
+ | `[workspace-dir]` | 项目目录路径(含 package.json),默认当前目录 |
36
+ | `--no-bin` | 输出纯文本 .code3d.js(默认输出 gzip 的 .code3d.js.bin) |
37
+
38
+ ## Uninstall
39
+
40
+ ```bash
41
+ # 取消全局安装(移除 code3d-build 命令)
42
+ npm uninstall -g parapoly-runtime
43
+
44
+ # 查看全局安装位置
45
+ npm ls -g parapoly-runtime
46
+ ```
47
+
48
+ ## Build mode
49
+
50
+ This package was built in **production** mode.
@@ -0,0 +1,51 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * code3d-build CLI — code3d 工作区项目编译工具
4
+ *
5
+ * 用法:
6
+ * node code3d-build.js [workspace-dir] [options]
7
+ *
8
+ * 参数:
9
+ * workspace-dir 项目目录路径(包含 package.json),默认为当前目录
10
+ *
11
+ * 选项:
12
+ * --no-bin 强制输出纯文本 .code3d.js 文件(默认输出 gzip 压缩的 .code3d.js.bin)
13
+ *
14
+ * 配置文件:
15
+ * package.json (必须):
16
+ * {
17
+ * "name": "my-project", // 项目名称,也作为默认输出文件名
18
+ * "version": "1.0.0", // 版本号
19
+ * "main": "main.code3d.js" // 入口文件
20
+ * }
21
+ *
22
+ * code3d-workspace-config.json (可选):
23
+ * {
24
+ * "build": { // 编译配置
25
+ * "output": "dist", // 输出文件夹,默认 "dist"
26
+ * "filename": "custom-name", // 可选,覆盖输出文件名(不含扩展名)
27
+ * "bin": false // 可选,设为 false 则输出 .js 而非 .js.bin
28
+ * }
29
+ * }
30
+ *
31
+ * 输出文件名规则:
32
+ * - 基础名 = build.filename || name
33
+ * - 自动检测是否已包含 .code3d,避免重复
34
+ * - 默认 (bin): {基础名}.code3d.js.bin (gzip 压缩)
35
+ * - --no-bin: {基础名}.code3d.js (纯文本 JS)
36
+ *
37
+ * 示例:
38
+ * node code3d-build.js # 当前目录,输出 dist/{name}.code3d.js.bin
39
+ * node code3d-build.js ./my-project # 指定目录
40
+ * node code3d-build.js ./my-project --no-bin # 输出 dist/{name}.code3d.js
41
+ *
42
+ * 编译流程:
43
+ * 1. 读取 code3d-workspace.json
44
+ * 2. 递归收集所有 .code3d.js 和 .part3d 文件(跳过 node_modules/dist/.git)
45
+ * 3. 语法检查所有 .code3d.js 模块
46
+ * 4. 从 main 入口构建依赖图(支持远程 URL 和绝对路径)
47
+ * 5. 拓扑排序确定编译顺序
48
+ * 6. 生成 bundle(所有模块打包为单文件)
49
+ * 7. 根据模式输出 .bin 或 .js 文件
50
+ */
51
+ const fs=require("fs"),path=require("path"),https=require("https"),http=require("http"),zlib=require("zlib"),vm=require("vm");function fetchUrl(e){return new Promise((t,n)=>{(e.startsWith("https")?https:http).get(e,s=>{if(s.statusCode>=300&&s.statusCode<400&&s.headers.location)return void fetchUrl(s.headers.location).then(t,n);if(200!==s.statusCode)return void n(new Error(`HTTP ${s.statusCode} for ${e}`));let r="";s.setEncoding("utf-8"),s.on("data",e=>r+=e),s.on("end",()=>t(r)),s.on("error",n)}).on("error",n)})}function isUrl(e){return e.startsWith("http://")||e.startsWith("https://")}function extractDependencies(e){let t=[],n=e.split("\n");for(let e of n){if(e.trim().startsWith("//"))continue;let n,s=/use\s*\(\s*["']([^"']+)["']/g;for(;null!==(n=s.exec(e));)t.indexOf(n[1])<0&&t.push(n[1])}return t}function isAbsolutePath(e){return!!/^[a-zA-Z]:[/\\]/.test(e)||!!e.startsWith("/")}function normalizePath(e){return e.replace(/\\/g,"/")}function resolvePath(e,t){if(isUrl(t))return t;if(isAbsolutePath(t))return normalizePath(t);if(t.startsWith("./")||t.startsWith("../")){let n=e.replace(/\\/g,"/").split("/");n.pop();let s=t.split("/");for(let e of s)"."!==e&&""!==e&&(".."===e?n.pop():n.push(e));return"./"+n.filter(e=>""!==e&&"."!==e).join("/")}return t}async function buildDependencyGraph(e,t){let n=new Map,s=[t],r=new Set;for(;s.length>0;){let t=s.shift();if(r.has(t))continue;if(r.add(t),!e[t]&&isUrl(t)){console.log(` Fetching: ${t}`);let n=await fetchUrl(t);65279===n.charCodeAt(0)&&(n=n.slice(1)),e[t]=n}if(!e[t]&&isAbsolutePath(t)){let n=t.replace(/\//g,path.sep);if(fs.existsSync(n)){let s=fs.readFileSync(n,"utf-8");65279===s.charCodeAt(0)&&(s=s.slice(1)),e[t]=s}}let o=e[t];if(!o)throw new Error("File not found: "+t);if(t.endsWith(".part3d")){n.set(t,[]);continue}let i=extractDependencies(o),l=[];for(let e of i){let n=resolvePath(t,e);l.push(n),r.has(n)||s.push(n)}n.set(t,l)}return n}function topologicalSort(e){let t=[],n=new Set,s=new Set;function r(o){if(n.has(o))return;if(s.has(o))throw new Error("Circular dependency detected involving: "+o);s.add(o);let i=e.get(o)||[];for(let e of i)r(e);s.delete(o),n.add(o),t.push(o)}for(let t of e.keys())r(t);return t}function rewriteUsePaths(e,t){return e.replace(/use\s*\(\s*["']([^"']+)["']/g,(e,n)=>{let s=resolvePath(t,n);return`use(${JSON.stringify(s)}`})}function extractFunctionBody(e){let t=e.match(/let\s+main\s*=\s*function\s*\([^)]*\)\s*\{/);if(!t)return e;let n=t.index+t[0].length,s=1,r=n;for(let t=n;t<e.length;t++)if("{"===e[t])s++;else if("}"===e[t]&&(s--,0===s)){r=t;break}return e.substring(n,r).trim()}function checkSyntax(e,t){let n=[];if(!/let\s+main\s*=\s*function/.test(e))return n.push({line:1,column:1,message:"缺少 main 函数定义 (需要 'let main = function(...) { ... }')"}),n;let s=extractFunctionBody(e);/return\s+/.test(s)||n.push({line:null,column:null,message:"main 函数没有 return 语句"});try{let e=`(function(params, use, PartBlock3dDocument) { ${s} })`;new vm.Script(e,{filename:t})}catch(e){let t=e.stack?.match(/:(\d+)/)?.[1],s=e.message;n.push({line:t?parseInt(t):null,column:null,message:s})}return n}async function bundle(e,t){let n="./"+e.main,s=topologicalSort(await buildDependencyGraph(t,n)),r=!1;for(let e in t){if(!e.endsWith(".code3d.js"))continue;let n=checkSyntax(t[e],e);for(let t of n){r=!0;let n=t.line?` (行 ${t.line})`:"";console.error(` ✗ ${e}${n}: ${t.message}`)}}if(r)throw new Error("语法检查未通过,编译中止");console.log(" 语法检查通过");let o=[];o.push("let main = function() {"),o.push(" const __modules = {};"),o.push(" const __cache = {};"),o.push("");for(let e in t)if(e.endsWith(".part3d")){let n=JSON.stringify(t[e]),s=e.replace(/\\/g,"/");o.push(` __modules[${JSON.stringify(s)}] = function(params) {`),o.push(" let recompile = params?.recompile !== undefined ? params.recompile : false;"),o.push(" let color = params?.color;"),o.push(` return PartBlock3dDocument.create_doc_from_part3d(${n}, recompile, color);`),o.push(" };"),o.push("")}for(let e of s){if(e===n)continue;if(e.endsWith(".part3d"))continue;let s=extractFunctionBody(rewriteUsePaths(t[e],e)).replace(/\\/g,"\\\\").replace(/`/g,"\\`").replace(/\$\{/g,"\\${");o.push(` __modules[${JSON.stringify(e)}] = new Function("params", "use", "PartBlock3dDocument", \`${s}\`);`),o.push("")}o.push(" function use(path, params) {"),o.push(' let key = path + "|" + JSON.stringify(params || {});'),o.push(" if (__cache[key]) return __cache[key];"),o.push(' if (!__modules[path]) throw new Error("Module not found: " + path);'),o.push(" if (typeof postMessage === 'function') {"),o.push(" let label = path.endsWith('.part3d') ? '加载资源: ' : '编译模块: ';"),o.push(" try { postMessage({ cmd: 'log', log_type: 'debug', message: label + path }); } catch(e) {}"),o.push(" }"),o.push(" try {"),o.push(" __cache[key] = __modules[path](params, use, PartBlock3dDocument);"),o.push(" } catch(e) {"),o.push(" let lineInfo = '';"),o.push(" var stack = e.stack || '';"),o.push(" var m = stack.match(/<anonymous>:(\\d+):(\\d+)/);"),o.push(" if (m) lineInfo = ' (行 ' + (parseInt(m[1]) - 1) + ', 列 ' + m[2] + ')';"),o.push(" throw new Error('[' + path + lineInfo + '] ' + e.message);"),o.push(" }"),o.push(" return __cache[key];"),o.push(" }"),o.push("");let i=extractFunctionBody(rewriteUsePaths(t[n],n));return o.push(" // --- entry ---"),o.push(` ${i}`),o.push("}"),o.join("\n")}function computeOutputFilename(e,t){let n=e.build?.filename||e.name;return n=n.replace(/\.code3d\.js\.bin$/,"").replace(/\.code3d\.js$/,"").replace(/\.code3d$/,""),n+(t?".code3d.js.bin":".code3d.js")}async function main_cli(){let e=process.argv.slice(2),t=e.includes("--no-bin");e=e.filter(e=>"--bin"!==e&&"--no-bin"!==e);let n=e[0]||process.cwd();n=path.resolve(n);let s=path.join(n,"package.json");fs.existsSync(s)||(console.error("Error: package.json not found in "+n),process.exit(1));let r=JSON.parse(fs.readFileSync(s,"utf-8"));r.name&&r.main||(console.error("Error: package.json must contain 'name' and 'main' fields"),process.exit(1));let o={},i=path.join(n,"code3d-workspace-config.json");fs.existsSync(i)&&(o=JSON.parse(fs.readFileSync(i,"utf-8")));let l={name:r.name,version:r.version||"1.0.0",main:r.main,...o},a=!1!==l.build?.bin;t&&(a=!1),console.log(`Building project: ${l.name} v${l.version}`),console.log(`Entry: ${l.main}`);let c={};collectFiles(n,n,c),console.log(`Found ${Object.keys(c).length} module(s)`);let u=await bundle(l,c),p=(l.build?.output||"dist")+"/"+computeOutputFilename(l,a),h=path.join(n,p),f=path.dirname(h);fs.existsSync(f)||fs.mkdirSync(f,{recursive:!0});let d=Buffer.byteLength(u,"utf-8");if(a){let e=zlib.gzipSync(Buffer.from(u,"utf-8"),{level:9});fs.writeFileSync(h,e);let t=(100*(1-e.length/d)).toFixed(1);console.log(`Binary written to: ${p} (${(e.length/1024).toFixed(1)} KB, -${t}% from ${(d/1024).toFixed(1)} KB source)`)}else fs.writeFileSync(h,u,"utf-8"),console.log(`Bundle written to: ${p} (${(d/1024).toFixed(1)} KB)`);console.log("Done.")}function collectFiles(e,t,n){let s=fs.readdirSync(t);for(let r of s){let s=path.join(t,r);if(fs.statSync(s).isDirectory()){if("node_modules"===r||"dist"===r||".git"===r)continue;collectFiles(e,s,n)}else if(r.endsWith(".code3d.js")||r.endsWith(".part3d")){let t="./"+path.relative(e,s).replace(/\\/g,"/"),r=fs.readFileSync(s,"utf-8");65279===r.charCodeAt(0)&&(r=r.slice(1)),n[t]=r}}}main_cli().catch(e=>{console.error("Build failed:",e.message),process.exit(1)});
package/package.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "parapoly-runtime",
3
+ "version": "1.0.0",
4
+ "description": "ParaPoly 3D CAD Runtime — CSG modeling, sketch, and project builder",
5
+ "main": "./parapoly_runtime.js",
6
+ "types": "./parapoly_runtime.d.ts",
7
+ "bin": {
8
+ "code3d-build": "./cli/code3d-build.js"
9
+ },
10
+ "keywords": [
11
+ "3d",
12
+ "cad",
13
+ "csg",
14
+ "modeling",
15
+ "parapoly",
16
+ "code3d"
17
+ ],
18
+ "files": [
19
+ "parapoly_runtime.js",
20
+ "parapoly_runtime.d.ts",
21
+ "cli/",
22
+ "README.md"
23
+ ]
24
+ }