create-ifc-lite 1.14.4 → 1.14.5
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 +2 -2
- package/dist/index.js +8 -14
- package/dist/templates/basic.js +4 -4
- package/dist/templates/react.d.ts +4 -0
- package/dist/templates/react.js +580 -0
- package/dist/templates/server-native.js +17 -8
- package/dist/templates/server.js +17 -8
- package/dist/utils/config-fixers.d.ts +3 -10
- package/dist/utils/config-fixers.js +63 -19
- package/package.json +2 -3
package/README.md
CHANGED
|
@@ -21,7 +21,7 @@ npm install
|
|
|
21
21
|
npm run parse ./model.ifc
|
|
22
22
|
```
|
|
23
23
|
|
|
24
|
-
**React** - React + Vite project with drag-and-drop
|
|
24
|
+
**React** - React + Vite project with WebGPU rendering and drag-and-drop loading:
|
|
25
25
|
|
|
26
26
|
```bash
|
|
27
27
|
npx create-ifc-lite my-viewer --template react
|
|
@@ -34,7 +34,7 @@ npm run dev
|
|
|
34
34
|
|
|
35
35
|
| Flag | Description |
|
|
36
36
|
|------|-------------|
|
|
37
|
-
| `--template <type>` | Template to use: `basic`, `react` (default: `basic`) |
|
|
37
|
+
| `--template <type>` | Template to use: `basic`, `threejs`, `babylonjs`, `react`, `server`, `server-native` (default: `basic`) |
|
|
38
38
|
| `--help` | Show help |
|
|
39
39
|
|
|
40
40
|
## Learn More
|
package/dist/index.js
CHANGED
|
@@ -7,10 +7,9 @@ import { join } from 'path';
|
|
|
7
7
|
import { createBasicTemplate } from './templates/basic.js';
|
|
8
8
|
import { createThreejsTemplate } from './templates/threejs.js';
|
|
9
9
|
import { createBabylonjsTemplate } from './templates/babylonjs.js';
|
|
10
|
+
import { createReactTemplate } from './templates/react.js';
|
|
10
11
|
import { createServerTemplate } from './templates/server.js';
|
|
11
12
|
import { createServerNativeTemplate } from './templates/server-native.js';
|
|
12
|
-
import { fixViewerTemplate } from './utils/config-fixers.js';
|
|
13
|
-
import { downloadViewer } from './utils/download.js';
|
|
14
13
|
const TEMPLATES = {
|
|
15
14
|
basic: 'basic',
|
|
16
15
|
threejs: 'threejs',
|
|
@@ -42,7 +41,7 @@ function printUsage() {
|
|
|
42
41
|
basic Minimal TypeScript project for parsing IFC files
|
|
43
42
|
threejs Three.js viewer (WebGL, no WebGPU required)
|
|
44
43
|
babylonjs Babylon.js viewer (WebGL, no WebGPU required)
|
|
45
|
-
react
|
|
44
|
+
react React + Vite viewer with WebGPU rendering
|
|
46
45
|
server Docker-based IFC processing server with TypeScript client
|
|
47
46
|
server-native Native binary server (no Docker required)
|
|
48
47
|
`);
|
|
@@ -87,16 +86,8 @@ async function main() {
|
|
|
87
86
|
createBabylonjsTemplate(targetDir, projectName);
|
|
88
87
|
}
|
|
89
88
|
else if (template === 'react') {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
if (success) {
|
|
93
|
-
fixViewerTemplate(targetDir, projectName);
|
|
94
|
-
}
|
|
95
|
-
else {
|
|
96
|
-
console.error(' Failed to download viewer. Creating minimal fallback...');
|
|
97
|
-
mkdirSync(targetDir, { recursive: true });
|
|
98
|
-
createBasicTemplate(targetDir, projectName);
|
|
99
|
-
}
|
|
89
|
+
mkdirSync(targetDir, { recursive: true });
|
|
90
|
+
createReactTemplate(targetDir, projectName);
|
|
100
91
|
}
|
|
101
92
|
else if (template === 'server') {
|
|
102
93
|
mkdirSync(targetDir, { recursive: true });
|
|
@@ -133,4 +124,7 @@ async function main() {
|
|
|
133
124
|
}
|
|
134
125
|
console.log();
|
|
135
126
|
}
|
|
136
|
-
main().catch(
|
|
127
|
+
main().catch((error) => {
|
|
128
|
+
console.error(error instanceof Error ? `\n ${error.message}\n` : error);
|
|
129
|
+
process.exit(1);
|
|
130
|
+
});
|
package/dist/templates/basic.js
CHANGED
|
@@ -3,23 +3,23 @@
|
|
|
3
3
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
|
4
4
|
import { mkdirSync, writeFileSync } from 'fs';
|
|
5
5
|
import { join } from 'path';
|
|
6
|
-
import {
|
|
6
|
+
import { getPackageVersion } from '../utils/config-fixers.js';
|
|
7
7
|
/**
|
|
8
8
|
* Scaffold a minimal TypeScript project for parsing IFC files.
|
|
9
9
|
*/
|
|
10
10
|
export function createBasicTemplate(targetDir, projectName) {
|
|
11
|
-
const
|
|
11
|
+
const parserVersion = getPackageVersion('@ifc-lite/parser');
|
|
12
12
|
// package.json
|
|
13
13
|
writeFileSync(join(targetDir, 'package.json'), JSON.stringify({
|
|
14
14
|
name: projectName,
|
|
15
|
-
version:
|
|
15
|
+
version: parserVersion.replace('^', ''),
|
|
16
16
|
type: 'module',
|
|
17
17
|
scripts: {
|
|
18
18
|
parse: 'npx tsx src/index.ts',
|
|
19
19
|
build: 'tsc',
|
|
20
20
|
},
|
|
21
21
|
dependencies: {
|
|
22
|
-
'@ifc-lite/parser':
|
|
22
|
+
'@ifc-lite/parser': parserVersion,
|
|
23
23
|
},
|
|
24
24
|
devDependencies: {
|
|
25
25
|
'@types/node': '^22.0.0',
|
|
@@ -0,0 +1,580 @@
|
|
|
1
|
+
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
|
4
|
+
import { mkdirSync, writeFileSync } from 'fs';
|
|
5
|
+
import { join } from 'path';
|
|
6
|
+
import { getPackageVersion } from '../utils/config-fixers.js';
|
|
7
|
+
const LICENSE_HEADER = `/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
8
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
9
|
+
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
|
10
|
+
|
|
11
|
+
`;
|
|
12
|
+
function writeSourceFile(targetDir, relativePath, content) {
|
|
13
|
+
writeFileSync(join(targetDir, relativePath), `${LICENSE_HEADER}${content}`);
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Scaffold a standalone React + Vite WebGPU viewer.
|
|
17
|
+
*/
|
|
18
|
+
export function createReactTemplate(targetDir, projectName) {
|
|
19
|
+
const geometryVersion = getPackageVersion('@ifc-lite/geometry');
|
|
20
|
+
const rendererVersion = getPackageVersion('@ifc-lite/renderer');
|
|
21
|
+
writeFileSync(join(targetDir, 'package.json'), JSON.stringify({
|
|
22
|
+
name: projectName,
|
|
23
|
+
version: '0.1.0',
|
|
24
|
+
private: true,
|
|
25
|
+
type: 'module',
|
|
26
|
+
scripts: {
|
|
27
|
+
dev: 'vite',
|
|
28
|
+
build: 'tsc && vite build',
|
|
29
|
+
typecheck: 'tsc --noEmit',
|
|
30
|
+
preview: 'vite preview',
|
|
31
|
+
},
|
|
32
|
+
dependencies: {
|
|
33
|
+
'@ifc-lite/geometry': geometryVersion,
|
|
34
|
+
'@ifc-lite/renderer': rendererVersion,
|
|
35
|
+
react: '^18.2.0',
|
|
36
|
+
'react-dom': '^18.2.0',
|
|
37
|
+
},
|
|
38
|
+
devDependencies: {
|
|
39
|
+
'@types/react': '^18.2.0',
|
|
40
|
+
'@types/react-dom': '^18.2.0',
|
|
41
|
+
'@vitejs/plugin-react': '^4.2.0',
|
|
42
|
+
typescript: '^5.3.0',
|
|
43
|
+
vite: '^5.0.0',
|
|
44
|
+
},
|
|
45
|
+
}, null, 2));
|
|
46
|
+
writeFileSync(join(targetDir, 'tsconfig.json'), JSON.stringify({
|
|
47
|
+
compilerOptions: {
|
|
48
|
+
target: 'ES2022',
|
|
49
|
+
useDefineForClassFields: true,
|
|
50
|
+
lib: ['ES2022', 'DOM', 'DOM.Iterable'],
|
|
51
|
+
module: 'ESNext',
|
|
52
|
+
skipLibCheck: true,
|
|
53
|
+
moduleResolution: 'bundler',
|
|
54
|
+
allowImportingTsExtensions: false,
|
|
55
|
+
resolveJsonModule: true,
|
|
56
|
+
isolatedModules: true,
|
|
57
|
+
noEmit: true,
|
|
58
|
+
jsx: 'react-jsx',
|
|
59
|
+
strict: true,
|
|
60
|
+
esModuleInterop: true,
|
|
61
|
+
},
|
|
62
|
+
include: ['src'],
|
|
63
|
+
}, null, 2));
|
|
64
|
+
writeSourceFile(targetDir, 'vite.config.ts', `import { defineConfig } from 'vite';
|
|
65
|
+
import react from '@vitejs/plugin-react';
|
|
66
|
+
|
|
67
|
+
const isolationHeaders = {
|
|
68
|
+
'Cross-Origin-Opener-Policy': 'same-origin',
|
|
69
|
+
'Cross-Origin-Embedder-Policy': 'require-corp',
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
export default defineConfig({
|
|
73
|
+
plugins: [react()],
|
|
74
|
+
optimizeDeps: {
|
|
75
|
+
exclude: ['@ifc-lite/wasm'],
|
|
76
|
+
},
|
|
77
|
+
server: {
|
|
78
|
+
headers: isolationHeaders,
|
|
79
|
+
},
|
|
80
|
+
preview: {
|
|
81
|
+
headers: isolationHeaders,
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
`);
|
|
85
|
+
writeFileSync(join(targetDir, 'index.html'), `<!DOCTYPE html>
|
|
86
|
+
<html lang="en">
|
|
87
|
+
<head>
|
|
88
|
+
<meta charset="UTF-8" />
|
|
89
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
90
|
+
<title>${projectName}</title>
|
|
91
|
+
</head>
|
|
92
|
+
<body>
|
|
93
|
+
<div id="root"></div>
|
|
94
|
+
<script type="module" src="/src/main.tsx"></script>
|
|
95
|
+
</body>
|
|
96
|
+
</html>
|
|
97
|
+
`);
|
|
98
|
+
mkdirSync(join(targetDir, 'src'));
|
|
99
|
+
writeSourceFile(targetDir, 'src/main.tsx', `import React from 'react';
|
|
100
|
+
import ReactDOM from 'react-dom/client';
|
|
101
|
+
import App from './App.js';
|
|
102
|
+
import './styles.css';
|
|
103
|
+
|
|
104
|
+
ReactDOM.createRoot(document.getElementById('root')!).render(
|
|
105
|
+
<React.StrictMode>
|
|
106
|
+
<App />
|
|
107
|
+
</React.StrictMode>,
|
|
108
|
+
);
|
|
109
|
+
`);
|
|
110
|
+
writeSourceFile(targetDir, 'src/App.tsx', `import { useCallback, useEffect, useRef, useState, type ChangeEvent, type DragEvent } from 'react';
|
|
111
|
+
import { GeometryProcessor } from '@ifc-lite/geometry';
|
|
112
|
+
import { Renderer } from '@ifc-lite/renderer';
|
|
113
|
+
|
|
114
|
+
type ViewerSession = {
|
|
115
|
+
renderer: Renderer;
|
|
116
|
+
destroy: () => void;
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
function setupCameraControls(canvas: HTMLCanvasElement, renderer: Renderer): () => void {
|
|
120
|
+
const camera = renderer.getCamera();
|
|
121
|
+
let isDragging = false;
|
|
122
|
+
let isPanning = false;
|
|
123
|
+
let lastX = 0;
|
|
124
|
+
let lastY = 0;
|
|
125
|
+
|
|
126
|
+
const onMouseDown = (event: MouseEvent) => {
|
|
127
|
+
isDragging = true;
|
|
128
|
+
isPanning = event.button === 1 || event.button === 2 || event.shiftKey;
|
|
129
|
+
lastX = event.clientX;
|
|
130
|
+
lastY = event.clientY;
|
|
131
|
+
canvas.style.cursor = isPanning ? 'move' : 'grabbing';
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
const onMouseMove = (event: MouseEvent) => {
|
|
135
|
+
if (!isDragging) return;
|
|
136
|
+
const deltaX = event.clientX - lastX;
|
|
137
|
+
const deltaY = event.clientY - lastY;
|
|
138
|
+
lastX = event.clientX;
|
|
139
|
+
lastY = event.clientY;
|
|
140
|
+
|
|
141
|
+
if (isPanning) {
|
|
142
|
+
camera.pan(deltaX, deltaY);
|
|
143
|
+
} else {
|
|
144
|
+
camera.orbit(deltaX, deltaY);
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
const stopDrag = () => {
|
|
149
|
+
isDragging = false;
|
|
150
|
+
isPanning = false;
|
|
151
|
+
canvas.style.cursor = 'grab';
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
const onWheel = (event: WheelEvent) => {
|
|
155
|
+
event.preventDefault();
|
|
156
|
+
camera.zoom(event.deltaY);
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
const onContextMenu = (event: MouseEvent) => {
|
|
160
|
+
event.preventDefault();
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
canvas.addEventListener('mousedown', onMouseDown);
|
|
164
|
+
window.addEventListener('mousemove', onMouseMove);
|
|
165
|
+
window.addEventListener('mouseup', stopDrag);
|
|
166
|
+
canvas.addEventListener('mouseleave', stopDrag);
|
|
167
|
+
canvas.addEventListener('wheel', onWheel, { passive: false });
|
|
168
|
+
canvas.addEventListener('contextmenu', onContextMenu);
|
|
169
|
+
canvas.style.cursor = 'grab';
|
|
170
|
+
|
|
171
|
+
return () => {
|
|
172
|
+
canvas.removeEventListener('mousedown', onMouseDown);
|
|
173
|
+
window.removeEventListener('mousemove', onMouseMove);
|
|
174
|
+
window.removeEventListener('mouseup', stopDrag);
|
|
175
|
+
canvas.removeEventListener('mouseleave', stopDrag);
|
|
176
|
+
canvas.removeEventListener('wheel', onWheel);
|
|
177
|
+
canvas.removeEventListener('contextmenu', onContextMenu);
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
async function createViewer(canvas: HTMLCanvasElement): Promise<ViewerSession> {
|
|
182
|
+
const renderer = new Renderer(canvas);
|
|
183
|
+
await renderer.init();
|
|
184
|
+
|
|
185
|
+
const resize = () => {
|
|
186
|
+
const rect = canvas.getBoundingClientRect();
|
|
187
|
+
const width = Math.max(1, Math.floor(rect.width));
|
|
188
|
+
const height = Math.max(1, Math.floor(rect.height));
|
|
189
|
+
renderer.resize(width, height);
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
resize();
|
|
193
|
+
window.addEventListener('resize', resize);
|
|
194
|
+
const cleanupControls = setupCameraControls(canvas, renderer);
|
|
195
|
+
|
|
196
|
+
let destroyed = false;
|
|
197
|
+
let frameId = 0;
|
|
198
|
+
const loop = () => {
|
|
199
|
+
if (destroyed) return;
|
|
200
|
+
renderer.render();
|
|
201
|
+
frameId = requestAnimationFrame(loop);
|
|
202
|
+
};
|
|
203
|
+
loop();
|
|
204
|
+
|
|
205
|
+
return {
|
|
206
|
+
renderer,
|
|
207
|
+
destroy: () => {
|
|
208
|
+
destroyed = true;
|
|
209
|
+
cancelAnimationFrame(frameId);
|
|
210
|
+
cleanupControls();
|
|
211
|
+
window.removeEventListener('resize', resize);
|
|
212
|
+
renderer.destroy();
|
|
213
|
+
},
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
export default function App() {
|
|
218
|
+
const canvasRef = useRef<HTMLCanvasElement | null>(null);
|
|
219
|
+
const fileInputRef = useRef<HTMLInputElement | null>(null);
|
|
220
|
+
const processorRef = useRef<GeometryProcessor | null>(null);
|
|
221
|
+
const viewerRef = useRef<ViewerSession | null>(null);
|
|
222
|
+
const loadIdRef = useRef(0);
|
|
223
|
+
const [status, setStatus] = useState('Initializing WebGPU viewer...');
|
|
224
|
+
const [ready, setReady] = useState(false);
|
|
225
|
+
const [busy, setBusy] = useState(true);
|
|
226
|
+
|
|
227
|
+
useEffect(() => {
|
|
228
|
+
let cancelled = false;
|
|
229
|
+
const initId = ++loadIdRef.current;
|
|
230
|
+
|
|
231
|
+
const init = async () => {
|
|
232
|
+
if (!canvasRef.current) return;
|
|
233
|
+
if (!('gpu' in navigator)) {
|
|
234
|
+
setStatus('WebGPU is not available in this browser. Try a recent Chromium-based browser.');
|
|
235
|
+
setBusy(false);
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
try {
|
|
240
|
+
const processor = new GeometryProcessor();
|
|
241
|
+
await processor.init();
|
|
242
|
+
if (cancelled || initId !== loadIdRef.current) return;
|
|
243
|
+
|
|
244
|
+
processorRef.current = processor;
|
|
245
|
+
const viewer = await createViewer(canvasRef.current);
|
|
246
|
+
if (cancelled || initId !== loadIdRef.current) {
|
|
247
|
+
viewer.destroy();
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
viewerRef.current = viewer;
|
|
252
|
+
setReady(true);
|
|
253
|
+
setBusy(false);
|
|
254
|
+
setStatus('Drop an IFC file or choose one to start rendering.');
|
|
255
|
+
} catch (error) {
|
|
256
|
+
console.error(error);
|
|
257
|
+
setBusy(false);
|
|
258
|
+
setStatus(error instanceof Error ? error.message : 'Failed to initialize viewer.');
|
|
259
|
+
}
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
void init();
|
|
263
|
+
|
|
264
|
+
return () => {
|
|
265
|
+
cancelled = true;
|
|
266
|
+
loadIdRef.current += 1;
|
|
267
|
+
viewerRef.current?.destroy();
|
|
268
|
+
viewerRef.current = null;
|
|
269
|
+
};
|
|
270
|
+
}, []);
|
|
271
|
+
|
|
272
|
+
const loadFile = useCallback(async (file: File) => {
|
|
273
|
+
if (!canvasRef.current || !processorRef.current || !ready || busy) return;
|
|
274
|
+
const loadId = ++loadIdRef.current;
|
|
275
|
+
|
|
276
|
+
setBusy(true);
|
|
277
|
+
setStatus('Preparing renderer...');
|
|
278
|
+
|
|
279
|
+
try {
|
|
280
|
+
const previousViewer = viewerRef.current;
|
|
281
|
+
const viewer = await createViewer(canvasRef.current);
|
|
282
|
+
if (loadId !== loadIdRef.current) {
|
|
283
|
+
viewer.destroy();
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
previousViewer?.destroy();
|
|
288
|
+
viewerRef.current = viewer;
|
|
289
|
+
const renderer = viewer.renderer;
|
|
290
|
+
const processor = processorRef.current;
|
|
291
|
+
if (!processor) {
|
|
292
|
+
throw new Error('Geometry processor is no longer available.');
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const bytes = new Uint8Array(await file.arrayBuffer());
|
|
296
|
+
|
|
297
|
+
let loadedMeshes = 0;
|
|
298
|
+
setStatus('Streaming geometry...');
|
|
299
|
+
|
|
300
|
+
for await (const event of processor.processStreaming(bytes)) {
|
|
301
|
+
if (loadId !== loadIdRef.current) return;
|
|
302
|
+
|
|
303
|
+
if (event.type === 'batch') {
|
|
304
|
+
renderer.addMeshes(event.meshes, true);
|
|
305
|
+
loadedMeshes = event.totalSoFar;
|
|
306
|
+
setStatus('Loaded ' + loadedMeshes + ' meshes...');
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
if (event.type === 'complete') {
|
|
310
|
+
renderer.fitToView();
|
|
311
|
+
setStatus(file.name + ' loaded with ' + event.totalMeshes + ' meshes.');
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
} catch (error) {
|
|
315
|
+
console.error(error);
|
|
316
|
+
if (loadId === loadIdRef.current) {
|
|
317
|
+
setStatus(error instanceof Error ? error.message : 'Failed to load IFC file.');
|
|
318
|
+
}
|
|
319
|
+
} finally {
|
|
320
|
+
if (loadId === loadIdRef.current) {
|
|
321
|
+
setBusy(false);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}, [busy, ready]);
|
|
325
|
+
|
|
326
|
+
const onFileChange = useCallback(async (event: ChangeEvent<HTMLInputElement>) => {
|
|
327
|
+
const file = event.target.files?.[0];
|
|
328
|
+
if (!file) return;
|
|
329
|
+
await loadFile(file);
|
|
330
|
+
event.target.value = '';
|
|
331
|
+
}, [loadFile]);
|
|
332
|
+
|
|
333
|
+
const onDrop = useCallback(async (event: DragEvent<HTMLElement>) => {
|
|
334
|
+
event.preventDefault();
|
|
335
|
+
if (!ready || busy) return;
|
|
336
|
+
const file = event.dataTransfer.files?.[0];
|
|
337
|
+
if (file) {
|
|
338
|
+
await loadFile(file);
|
|
339
|
+
}
|
|
340
|
+
}, [busy, loadFile, ready]);
|
|
341
|
+
|
|
342
|
+
const openFilePicker = useCallback(() => {
|
|
343
|
+
if (!ready || busy) return;
|
|
344
|
+
fileInputRef.current?.click();
|
|
345
|
+
}, [busy, ready]);
|
|
346
|
+
|
|
347
|
+
return (
|
|
348
|
+
<div className="app">
|
|
349
|
+
<aside className="sidebar">
|
|
350
|
+
<div>
|
|
351
|
+
<p className="eyebrow">IFC-Lite</p>
|
|
352
|
+
<h1>${projectName}</h1>
|
|
353
|
+
<p className="muted">
|
|
354
|
+
Standalone React + Vite viewer powered by IFC-Lite geometry streaming and WebGPU rendering.
|
|
355
|
+
</p>
|
|
356
|
+
</div>
|
|
357
|
+
|
|
358
|
+
<input
|
|
359
|
+
ref={fileInputRef}
|
|
360
|
+
className="visuallyHiddenInput"
|
|
361
|
+
type="file"
|
|
362
|
+
accept=".ifc"
|
|
363
|
+
disabled={!ready || busy}
|
|
364
|
+
aria-label="Choose IFC file"
|
|
365
|
+
onChange={(event) => void onFileChange(event)}
|
|
366
|
+
/>
|
|
367
|
+
<button
|
|
368
|
+
type="button"
|
|
369
|
+
className="uploadButton"
|
|
370
|
+
disabled={!ready || busy}
|
|
371
|
+
aria-disabled={!ready || busy}
|
|
372
|
+
onClick={openFilePicker}
|
|
373
|
+
>
|
|
374
|
+
{busy ? 'Working…' : 'Choose IFC File'}
|
|
375
|
+
</button>
|
|
376
|
+
|
|
377
|
+
<div className="panel">
|
|
378
|
+
<p className="label">Status</p>
|
|
379
|
+
<p>{status}</p>
|
|
380
|
+
</div>
|
|
381
|
+
|
|
382
|
+
<div className="panel">
|
|
383
|
+
<p className="label">Controls</p>
|
|
384
|
+
<ul>
|
|
385
|
+
<li>Left drag: orbit</li>
|
|
386
|
+
<li>Shift / middle / right drag: pan</li>
|
|
387
|
+
<li>Wheel: zoom</li>
|
|
388
|
+
<li>Drop an IFC anywhere on the viewport</li>
|
|
389
|
+
</ul>
|
|
390
|
+
</div>
|
|
391
|
+
</aside>
|
|
392
|
+
|
|
393
|
+
<main className="viewportShell" onDragOver={(event) => event.preventDefault()} onDrop={(event) => void onDrop(event)}>
|
|
394
|
+
<canvas ref={canvasRef} className="viewport" />
|
|
395
|
+
</main>
|
|
396
|
+
</div>
|
|
397
|
+
);
|
|
398
|
+
}
|
|
399
|
+
`);
|
|
400
|
+
writeSourceFile(targetDir, 'src/styles.css', `:root {
|
|
401
|
+
color-scheme: dark;
|
|
402
|
+
font-family: Inter, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
403
|
+
background: #0b1220;
|
|
404
|
+
color: #e5eefc;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
* {
|
|
408
|
+
box-sizing: border-box;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
html,
|
|
412
|
+
body,
|
|
413
|
+
#root {
|
|
414
|
+
margin: 0;
|
|
415
|
+
min-height: 100%;
|
|
416
|
+
height: 100%;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
body {
|
|
420
|
+
background:
|
|
421
|
+
radial-gradient(circle at top, rgba(59, 130, 246, 0.18), transparent 30%),
|
|
422
|
+
#0b1220;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
.app {
|
|
426
|
+
display: grid;
|
|
427
|
+
grid-template-columns: 320px minmax(0, 1fr);
|
|
428
|
+
min-height: 100vh;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
.sidebar {
|
|
432
|
+
display: flex;
|
|
433
|
+
flex-direction: column;
|
|
434
|
+
gap: 1rem;
|
|
435
|
+
padding: 1.5rem;
|
|
436
|
+
border-right: 1px solid rgba(148, 163, 184, 0.18);
|
|
437
|
+
background: rgba(15, 23, 42, 0.92);
|
|
438
|
+
backdrop-filter: blur(16px);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
.eyebrow,
|
|
442
|
+
.label {
|
|
443
|
+
margin: 0 0 0.5rem;
|
|
444
|
+
color: #93c5fd;
|
|
445
|
+
font-size: 0.8rem;
|
|
446
|
+
font-weight: 700;
|
|
447
|
+
letter-spacing: 0.08em;
|
|
448
|
+
text-transform: uppercase;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
.sidebar h1 {
|
|
452
|
+
margin: 0 0 0.75rem;
|
|
453
|
+
font-size: 1.8rem;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
.muted {
|
|
457
|
+
margin: 0;
|
|
458
|
+
color: #94a3b8;
|
|
459
|
+
line-height: 1.5;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
.uploadButton {
|
|
463
|
+
display: inline-flex;
|
|
464
|
+
align-items: center;
|
|
465
|
+
justify-content: center;
|
|
466
|
+
min-height: 3rem;
|
|
467
|
+
padding: 0.9rem 1rem;
|
|
468
|
+
border-radius: 0.9rem;
|
|
469
|
+
background: linear-gradient(135deg, #2563eb, #0ea5e9);
|
|
470
|
+
color: white;
|
|
471
|
+
font-weight: 700;
|
|
472
|
+
cursor: pointer;
|
|
473
|
+
transition: transform 0.15s ease, opacity 0.15s ease;
|
|
474
|
+
border: none;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
.uploadButton:hover:not(:disabled) {
|
|
478
|
+
transform: translateY(-1px);
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
.uploadButton:disabled {
|
|
482
|
+
opacity: 0.6;
|
|
483
|
+
cursor: not-allowed;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
.visuallyHiddenInput {
|
|
487
|
+
position: absolute;
|
|
488
|
+
width: 1px;
|
|
489
|
+
height: 1px;
|
|
490
|
+
padding: 0;
|
|
491
|
+
margin: -1px;
|
|
492
|
+
overflow: hidden;
|
|
493
|
+
clip: rect(0, 0, 0, 0);
|
|
494
|
+
white-space: nowrap;
|
|
495
|
+
border: 0;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
.panel {
|
|
499
|
+
padding: 1rem;
|
|
500
|
+
border: 1px solid rgba(148, 163, 184, 0.18);
|
|
501
|
+
border-radius: 1rem;
|
|
502
|
+
background: rgba(15, 23, 42, 0.7);
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
.panel p {
|
|
506
|
+
margin: 0;
|
|
507
|
+
line-height: 1.5;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
.panel ul {
|
|
511
|
+
margin: 0;
|
|
512
|
+
padding-left: 1.1rem;
|
|
513
|
+
color: #cbd5e1;
|
|
514
|
+
line-height: 1.6;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
.viewportShell {
|
|
518
|
+
position: relative;
|
|
519
|
+
min-width: 0;
|
|
520
|
+
min-height: 100vh;
|
|
521
|
+
padding: 1rem;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
.viewport {
|
|
525
|
+
display: block;
|
|
526
|
+
width: 100%;
|
|
527
|
+
height: calc(100vh - 2rem);
|
|
528
|
+
border-radius: 1rem;
|
|
529
|
+
border: 1px solid rgba(148, 163, 184, 0.16);
|
|
530
|
+
background: linear-gradient(180deg, #020617, #0f172a);
|
|
531
|
+
outline: none;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
@media (max-width: 960px) {
|
|
535
|
+
.app {
|
|
536
|
+
grid-template-columns: 1fr;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
.sidebar {
|
|
540
|
+
border-right: none;
|
|
541
|
+
border-bottom: 1px solid rgba(148, 163, 184, 0.18);
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
.viewportShell {
|
|
545
|
+
min-height: 60vh;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
.viewport {
|
|
549
|
+
height: 60vh;
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
`);
|
|
553
|
+
writeFileSync(join(targetDir, 'README.md'), `# ${projectName}
|
|
554
|
+
|
|
555
|
+
React + Vite IFC viewer using [IFC-Lite](https://github.com/louistrue/ifc-lite).
|
|
556
|
+
|
|
557
|
+
## Quick Start
|
|
558
|
+
|
|
559
|
+
\`\`\`bash
|
|
560
|
+
npm install
|
|
561
|
+
npm run dev
|
|
562
|
+
\`\`\`
|
|
563
|
+
|
|
564
|
+
Open the app in your browser and drop an IFC file onto the viewport.
|
|
565
|
+
|
|
566
|
+
## Features
|
|
567
|
+
|
|
568
|
+
- React + Vite developer experience
|
|
569
|
+
- IFC-Lite geometry streaming for progressive loading
|
|
570
|
+
- WebGPU rendering via \`@ifc-lite/renderer\`
|
|
571
|
+
- Drag-and-drop or file picker loading
|
|
572
|
+
- Orbit, pan, and zoom camera controls
|
|
573
|
+
|
|
574
|
+
## Learn More
|
|
575
|
+
|
|
576
|
+
- [IFC-Lite Documentation](https://louistrue.github.io/ifc-lite/)
|
|
577
|
+
- [Rendering Guide](https://louistrue.github.io/ifc-lite/guide/rendering/)
|
|
578
|
+
- [Geometry Guide](https://louistrue.github.io/ifc-lite/guide/geometry/)
|
|
579
|
+
`);
|
|
580
|
+
}
|
|
@@ -3,13 +3,14 @@
|
|
|
3
3
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
|
4
4
|
import { mkdirSync, writeFileSync } from 'fs';
|
|
5
5
|
import { join } from 'path';
|
|
6
|
-
import {
|
|
6
|
+
import { getPackageVersion } from '../utils/config-fixers.js';
|
|
7
7
|
/**
|
|
8
8
|
* Scaffold a native-binary IFC processing server with TypeScript client examples.
|
|
9
9
|
* No Docker required -- the server binary is downloaded and run via npm scripts.
|
|
10
10
|
*/
|
|
11
11
|
export function createServerNativeTemplate(targetDir, projectName) {
|
|
12
|
-
const
|
|
12
|
+
const serverBinVersion = getPackageVersion('@ifc-lite/server-bin');
|
|
13
|
+
const serverClientVersion = getPackageVersion('@ifc-lite/server-client');
|
|
13
14
|
// package.json
|
|
14
15
|
writeFileSync(join(targetDir, 'package.json'), JSON.stringify({
|
|
15
16
|
name: projectName,
|
|
@@ -26,8 +27,8 @@ export function createServerNativeTemplate(targetDir, projectName) {
|
|
|
26
27
|
'typecheck': 'tsc --noEmit',
|
|
27
28
|
},
|
|
28
29
|
dependencies: {
|
|
29
|
-
'@ifc-lite/server-bin':
|
|
30
|
-
'@ifc-lite/server-client':
|
|
30
|
+
'@ifc-lite/server-bin': serverBinVersion,
|
|
31
|
+
'@ifc-lite/server-client': serverClientVersion,
|
|
31
32
|
},
|
|
32
33
|
devDependencies: {
|
|
33
34
|
'typescript': '^5.3.0',
|
|
@@ -184,9 +185,13 @@ Example:
|
|
|
184
185
|
process.exit(1);
|
|
185
186
|
}
|
|
186
187
|
|
|
187
|
-
const
|
|
188
|
+
const nodeBuffer = readFileSync(ifcPath);
|
|
189
|
+
const buffer = nodeBuffer.buffer.slice(
|
|
190
|
+
nodeBuffer.byteOffset,
|
|
191
|
+
nodeBuffer.byteOffset + nodeBuffer.byteLength,
|
|
192
|
+
) as ArrayBuffer;
|
|
188
193
|
console.log(\`\\nParsing: \${ifcPath}\`);
|
|
189
|
-
console.log(\`File size: \${(
|
|
194
|
+
console.log(\`File size: \${(nodeBuffer.length / 1024 / 1024).toFixed(2)} MB\`);
|
|
190
195
|
|
|
191
196
|
const startTime = performance.now();
|
|
192
197
|
|
|
@@ -264,8 +269,12 @@ async function main() {
|
|
|
264
269
|
process.exit(1);
|
|
265
270
|
}
|
|
266
271
|
|
|
267
|
-
const
|
|
268
|
-
|
|
272
|
+
const nodeBuffer = readFileSync(ifcPath);
|
|
273
|
+
const buffer = nodeBuffer.buffer.slice(
|
|
274
|
+
nodeBuffer.byteOffset,
|
|
275
|
+
nodeBuffer.byteOffset + nodeBuffer.byteLength,
|
|
276
|
+
) as ArrayBuffer;
|
|
277
|
+
console.log(\`\\nStreaming: \${ifcPath} (\${(nodeBuffer.length / 1024 / 1024).toFixed(1)} MB)\`);
|
|
269
278
|
|
|
270
279
|
const startTime = performance.now();
|
|
271
280
|
let totalMeshes = 0;
|
package/dist/templates/server.js
CHANGED
|
@@ -3,12 +3,12 @@
|
|
|
3
3
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
|
4
4
|
import { mkdirSync, writeFileSync } from 'fs';
|
|
5
5
|
import { join } from 'path';
|
|
6
|
-
import {
|
|
6
|
+
import { getPackageVersion } from '../utils/config-fixers.js';
|
|
7
7
|
/**
|
|
8
8
|
* Scaffold a Docker-based IFC processing server with TypeScript client examples.
|
|
9
9
|
*/
|
|
10
10
|
export function createServerTemplate(targetDir, projectName) {
|
|
11
|
-
const
|
|
11
|
+
const serverClientVersion = getPackageVersion('@ifc-lite/server-client');
|
|
12
12
|
// docker-compose.yml - Production configuration
|
|
13
13
|
writeFileSync(join(targetDir, 'docker-compose.yml'), `# IFC-Lite Server - Production Configuration
|
|
14
14
|
# Start with: docker compose up -d
|
|
@@ -180,7 +180,7 @@ cache
|
|
|
180
180
|
'typecheck': 'tsc --noEmit',
|
|
181
181
|
},
|
|
182
182
|
dependencies: {
|
|
183
|
-
'@ifc-lite/server-client':
|
|
183
|
+
'@ifc-lite/server-client': serverClientVersion,
|
|
184
184
|
},
|
|
185
185
|
devDependencies: {
|
|
186
186
|
'typescript': '^5.3.0',
|
|
@@ -273,8 +273,12 @@ The server will:
|
|
|
273
273
|
|
|
274
274
|
// Read IFC file
|
|
275
275
|
console.log(\`\\nParsing: \${ifcPath}\`);
|
|
276
|
-
const
|
|
277
|
-
|
|
276
|
+
const nodeBuffer = readFileSync(ifcPath);
|
|
277
|
+
const buffer = nodeBuffer.buffer.slice(
|
|
278
|
+
nodeBuffer.byteOffset,
|
|
279
|
+
nodeBuffer.byteOffset + nodeBuffer.byteLength,
|
|
280
|
+
) as ArrayBuffer;
|
|
281
|
+
console.log(\`File size: \${(nodeBuffer.length / 1024 / 1024).toFixed(2)} MB\`);
|
|
278
282
|
|
|
279
283
|
// Parse with Parquet format (most efficient)
|
|
280
284
|
console.log('\\nSending to server...');
|
|
@@ -373,8 +377,12 @@ async function main() {
|
|
|
373
377
|
process.exit(1);
|
|
374
378
|
}
|
|
375
379
|
|
|
376
|
-
const
|
|
377
|
-
|
|
380
|
+
const nodeBuffer = readFileSync(ifcPath);
|
|
381
|
+
const buffer = nodeBuffer.buffer.slice(
|
|
382
|
+
nodeBuffer.byteOffset,
|
|
383
|
+
nodeBuffer.byteOffset + nodeBuffer.byteLength,
|
|
384
|
+
) as ArrayBuffer;
|
|
385
|
+
console.log(\`\\nStreaming: \${ifcPath} (\${(nodeBuffer.length / 1024 / 1024).toFixed(1)} MB)\`);
|
|
378
386
|
|
|
379
387
|
// Check Parquet support
|
|
380
388
|
const parquetAvailable = await client.isParquetSupported();
|
|
@@ -438,6 +446,8 @@ main().catch(console.error);
|
|
|
438
446
|
* See example.ts and example-stream.ts for usage examples.
|
|
439
447
|
*/
|
|
440
448
|
|
|
449
|
+
import { IfcServerClient } from '@ifc-lite/server-client';
|
|
450
|
+
|
|
441
451
|
export { IfcServerClient } from '@ifc-lite/server-client';
|
|
442
452
|
export type {
|
|
443
453
|
ServerConfig,
|
|
@@ -455,7 +465,6 @@ export const DEFAULT_SERVER_URL = 'http://localhost:3001';
|
|
|
455
465
|
* Create a pre-configured client for the local Docker server.
|
|
456
466
|
*/
|
|
457
467
|
export function createLocalClient(options?: { timeout?: number }) {
|
|
458
|
-
const { IfcServerClient } = require('@ifc-lite/server-client');
|
|
459
468
|
return new IfcServerClient({
|
|
460
469
|
baseUrl: process.env.SERVER_URL || DEFAULT_SERVER_URL,
|
|
461
470
|
timeout: options?.timeout ?? 300000,
|
|
@@ -1,17 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Fetch the latest published version of a specific npm package.
|
|
2
|
+
* Fetch the latest installable published version of a specific npm package.
|
|
3
3
|
* Results are cached so repeated calls for the same package are free.
|
|
4
|
-
*
|
|
4
|
+
* Throws when the registry is unreachable so scaffolds never emit broken
|
|
5
|
+
* placeholder versions.
|
|
5
6
|
*/
|
|
6
7
|
export declare function getPackageVersion(packageName: string): string;
|
|
7
|
-
/**
|
|
8
|
-
* Fetch the latest published version of @ifc-lite/parser from npm.
|
|
9
|
-
* Falls back to '^1.0.0' when the registry is unreachable.
|
|
10
|
-
*
|
|
11
|
-
* @deprecated Use getPackageVersion(packageName) to query each package
|
|
12
|
-
* individually and avoid version mismatches between packages.
|
|
13
|
-
*/
|
|
14
|
-
export declare function getLatestVersion(): string;
|
|
15
8
|
/**
|
|
16
9
|
* Rewrite the viewer's package.json so it works as a standalone project:
|
|
17
10
|
* - Set the project name
|
|
@@ -3,40 +3,84 @@
|
|
|
3
3
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
|
4
4
|
import { existsSync, readFileSync, writeFileSync, rmSync } from 'fs';
|
|
5
5
|
import { join } from 'path';
|
|
6
|
-
import {
|
|
6
|
+
import { execFileSync } from 'child_process';
|
|
7
7
|
/** Cache of npm-resolved versions to avoid redundant registry queries. */
|
|
8
8
|
const versionCache = new Map();
|
|
9
|
+
const publishedVersionsCache = new Map();
|
|
10
|
+
const VALID_PACKAGE_NAME = /^(?:@[\w.-]+\/)?[\w.-]+$/;
|
|
11
|
+
const NPM_TIMEOUT_MS = 30000;
|
|
12
|
+
const MAX_VERSION_CANDIDATES = 10;
|
|
13
|
+
function readJsonFromNpm(args) {
|
|
14
|
+
const result = execFileSync('npm', args, {
|
|
15
|
+
stdio: 'pipe',
|
|
16
|
+
timeout: NPM_TIMEOUT_MS,
|
|
17
|
+
}).toString().trim();
|
|
18
|
+
return result ? JSON.parse(result) : {};
|
|
19
|
+
}
|
|
20
|
+
function getPublishedVersions(packageName) {
|
|
21
|
+
if (!VALID_PACKAGE_NAME.test(packageName)) {
|
|
22
|
+
throw new Error(`Invalid package name: ${packageName}`);
|
|
23
|
+
}
|
|
24
|
+
if (publishedVersionsCache.has(packageName)) {
|
|
25
|
+
return publishedVersionsCache.get(packageName);
|
|
26
|
+
}
|
|
27
|
+
const json = readJsonFromNpm(['view', packageName, 'versions', '--json']);
|
|
28
|
+
const versions = Array.isArray(json) ? json : [json];
|
|
29
|
+
const set = new Set(versions.filter((value) => typeof value === 'string'));
|
|
30
|
+
publishedVersionsCache.set(packageName, set);
|
|
31
|
+
return set;
|
|
32
|
+
}
|
|
33
|
+
function extractPinnedVersion(range) {
|
|
34
|
+
const match = range.match(/\d+\.\d+\.\d+/);
|
|
35
|
+
return match ? match[0] : null;
|
|
36
|
+
}
|
|
37
|
+
function getVersionDependencies(packageName, version) {
|
|
38
|
+
const json = readJsonFromNpm(['view', `${packageName}@${version}`, 'dependencies', '--json']);
|
|
39
|
+
if (!json || typeof json !== 'object' || Array.isArray(json)) {
|
|
40
|
+
return {};
|
|
41
|
+
}
|
|
42
|
+
return json;
|
|
43
|
+
}
|
|
44
|
+
function isInstallablePublishedVersion(packageName, version) {
|
|
45
|
+
const dependencies = getVersionDependencies(packageName, version);
|
|
46
|
+
for (const [dependencyName, dependencyRange] of Object.entries(dependencies)) {
|
|
47
|
+
if (!dependencyName.startsWith('@ifc-lite/'))
|
|
48
|
+
continue;
|
|
49
|
+
const pinnedVersion = extractPinnedVersion(dependencyRange);
|
|
50
|
+
if (!pinnedVersion)
|
|
51
|
+
continue;
|
|
52
|
+
if (!getPublishedVersions(dependencyName).has(pinnedVersion)) {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
9
58
|
/**
|
|
10
|
-
* Fetch the latest published version of a specific npm package.
|
|
59
|
+
* Fetch the latest installable published version of a specific npm package.
|
|
11
60
|
* Results are cached so repeated calls for the same package are free.
|
|
12
|
-
*
|
|
61
|
+
* Throws when the registry is unreachable so scaffolds never emit broken
|
|
62
|
+
* placeholder versions.
|
|
13
63
|
*/
|
|
14
64
|
export function getPackageVersion(packageName) {
|
|
15
65
|
if (versionCache.has(packageName)) {
|
|
16
66
|
return versionCache.get(packageName);
|
|
17
67
|
}
|
|
18
68
|
try {
|
|
19
|
-
const
|
|
20
|
-
const
|
|
69
|
+
const versions = [...getPublishedVersions(packageName)];
|
|
70
|
+
const recentVersions = versions.slice(-MAX_VERSION_CANDIDATES).reverse();
|
|
71
|
+
const selectedVersion = recentVersions.find((version) => isInstallablePublishedVersion(packageName, version));
|
|
72
|
+
if (!selectedVersion) {
|
|
73
|
+
throw new Error(`No installable published version found for ${packageName}.`);
|
|
74
|
+
}
|
|
75
|
+
const version = `^${selectedVersion}`;
|
|
21
76
|
versionCache.set(packageName, version);
|
|
22
77
|
return version;
|
|
23
78
|
}
|
|
24
|
-
catch {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
return fallback;
|
|
79
|
+
catch (cause) {
|
|
80
|
+
throw new Error(`Failed to resolve the latest published version of ${packageName}. ` +
|
|
81
|
+
'Check your npm registry access and try again.', { cause });
|
|
28
82
|
}
|
|
29
83
|
}
|
|
30
|
-
/**
|
|
31
|
-
* Fetch the latest published version of @ifc-lite/parser from npm.
|
|
32
|
-
* Falls back to '^1.0.0' when the registry is unreachable.
|
|
33
|
-
*
|
|
34
|
-
* @deprecated Use getPackageVersion(packageName) to query each package
|
|
35
|
-
* individually and avoid version mismatches between packages.
|
|
36
|
-
*/
|
|
37
|
-
export function getLatestVersion() {
|
|
38
|
-
return getPackageVersion('@ifc-lite/parser');
|
|
39
|
-
}
|
|
40
84
|
/**
|
|
41
85
|
* Rewrite the viewer's package.json so it works as a standalone project:
|
|
42
86
|
* - Set the project name
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-ifc-lite",
|
|
3
|
-
"version": "1.14.
|
|
3
|
+
"version": "1.14.5",
|
|
4
4
|
"description": "Create IFC-Lite projects with one command",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -8,8 +8,7 @@
|
|
|
8
8
|
},
|
|
9
9
|
"main": "./dist/index.js",
|
|
10
10
|
"files": [
|
|
11
|
-
"dist"
|
|
12
|
-
"templates"
|
|
11
|
+
"dist"
|
|
13
12
|
],
|
|
14
13
|
"keywords": [
|
|
15
14
|
"ifc",
|