create-ifc-lite 1.15.0 → 1.16.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 +4 -4
- package/dist/index.js +6 -3
- package/dist/templates/babylonjs.js +5 -5
- package/dist/templates/basic.js +4 -4
- package/dist/templates/react.js +5 -5
- package/dist/templates/server-native.js +4 -4
- package/dist/templates/server.js +3 -3
- package/dist/templates/threejs.js +5 -10
- package/dist/utils/config-fixers.d.ts +3 -0
- package/dist/utils/config-fixers.js +53 -20
- package/dist/utils/dependency-lock.d.ts +2 -0
- package/dist/utils/dependency-lock.js +23 -0
- package/dist/utils/npm-command.d.ts +7 -0
- package/dist/utils/npm-command.js +13 -0
- package/dist/utils/published-worker-assets.d.ts +7 -0
- package/dist/utils/published-worker-assets.js +101 -0
- package/package.json +7 -3
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# create-ifc-lite
|
|
2
2
|
|
|
3
|
-
Scaffolds an IFClite project
|
|
3
|
+
Scaffolds an IFClite project with runnable code and a resolved dependency lock.
|
|
4
4
|
|
|
5
5
|
## Usage
|
|
6
6
|
|
|
@@ -8,18 +8,18 @@ Scaffolds an IFClite project in seconds. One command, working code.
|
|
|
8
8
|
npx create-ifc-lite <project-name> [--template <type>]
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
The scaffolder picks `basic` by default. It pins inspected IFClite releases, skips releases whose required worker files are missing, and checks their transitive IFClite dependencies. It resolves the complete dependency set with npm, checks peer constraints, and writes `package-lock.json` without running dependency install scripts. Run `npm ci` in the generated project to install that exact set; commit the lockfile so teammates and CI use the same versions.
|
|
12
12
|
|
|
13
13
|
## Templates
|
|
14
14
|
|
|
15
15
|
```bash
|
|
16
16
|
# Minimal TypeScript project — parse an IFC file from a script
|
|
17
17
|
npx create-ifc-lite my-app
|
|
18
|
-
cd my-app && npm
|
|
18
|
+
cd my-app && npm ci && npm run parse ./model.ifc
|
|
19
19
|
|
|
20
20
|
# WebGPU 3D viewer (React + Vite + drag-and-drop)
|
|
21
21
|
npx create-ifc-lite my-viewer --template react
|
|
22
|
-
cd my-viewer && npm
|
|
22
|
+
cd my-viewer && npm ci && npm run dev
|
|
23
23
|
|
|
24
24
|
# Three.js (WebGL) viewer
|
|
25
25
|
npx create-ifc-lite my-viewer --template threejs
|
package/dist/index.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import { existsSync, mkdirSync, rmSync, readFileSync } from 'fs';
|
|
6
6
|
import { join, dirname } from 'path';
|
|
7
7
|
import { fileURLToPath } from 'url';
|
|
8
|
+
import { createDependencyLock } from './utils/dependency-lock.js';
|
|
8
9
|
import { createBasicTemplate } from './templates/basic.js';
|
|
9
10
|
import { createThreejsTemplate } from './templates/threejs.js';
|
|
10
11
|
import { createBabylonjsTemplate } from './templates/babylonjs.js';
|
|
@@ -137,6 +138,8 @@ async function main() {
|
|
|
137
138
|
mkdirSync(targetDir, { recursive: true });
|
|
138
139
|
try {
|
|
139
140
|
TEMPLATES[template](targetDir, projectName);
|
|
141
|
+
console.log(' Resolving the dependency set and writing package-lock.json...');
|
|
142
|
+
createDependencyLock(targetDir);
|
|
140
143
|
}
|
|
141
144
|
catch (error) {
|
|
142
145
|
// Templates resolve their dependency versions from the npm registry as
|
|
@@ -149,16 +152,16 @@ async function main() {
|
|
|
149
152
|
console.log(` cd ${projectName}`);
|
|
150
153
|
if (template === 'server') {
|
|
151
154
|
console.log(` docker compose up -d`);
|
|
152
|
-
console.log(` npm
|
|
155
|
+
console.log(` npm ci && npm run example ./your-model.ifc`);
|
|
153
156
|
console.log(`\n Server will be available at http://localhost:3001`);
|
|
154
157
|
}
|
|
155
158
|
else if (template === 'server-native') {
|
|
156
|
-
console.log(` npm
|
|
159
|
+
console.log(` npm ci`);
|
|
157
160
|
console.log(` npm run server:start`);
|
|
158
161
|
console.log(`\n Server will be available at http://localhost:8080`);
|
|
159
162
|
}
|
|
160
163
|
else {
|
|
161
|
-
console.log(` npm
|
|
164
|
+
console.log(` npm ci`);
|
|
162
165
|
if (template === 'react' || template === 'threejs' || template === 'babylonjs') {
|
|
163
166
|
console.log(` npm run dev`);
|
|
164
167
|
}
|
|
@@ -3,14 +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 { getPinnedPackageVersion, MODULE_WORKER_CONFIG } from '../utils/config-fixers.js';
|
|
7
7
|
import { writeGitignore } from '../utils/gitignore.js';
|
|
8
8
|
/**
|
|
9
9
|
* Scaffold a Babylon.js IFC viewer project using @ifc-lite/geometry.
|
|
10
10
|
* No WebGPU required — renders via Babylon.js WebGL Engine.
|
|
11
11
|
*/
|
|
12
12
|
export function createBabylonjsTemplate(targetDir, projectName) {
|
|
13
|
-
const geometryVersion =
|
|
13
|
+
const geometryVersion = getPinnedPackageVersion('@ifc-lite/geometry');
|
|
14
14
|
// package.json
|
|
15
15
|
writeFileSync(join(targetDir, 'package.json'), JSON.stringify({
|
|
16
16
|
name: projectName,
|
|
@@ -49,7 +49,7 @@ export function createBabylonjsTemplate(targetDir, projectName) {
|
|
|
49
49
|
writeFileSync(join(targetDir, 'vite.config.ts'), `import { defineConfig } from 'vite';
|
|
50
50
|
|
|
51
51
|
export default defineConfig({
|
|
52
|
-
optimizeDeps: {
|
|
52
|
+
${MODULE_WORKER_CONFIG} optimizeDeps: {
|
|
53
53
|
exclude: ['@ifc-lite/wasm'],
|
|
54
54
|
},
|
|
55
55
|
server: {
|
|
@@ -249,13 +249,13 @@ Babylon.js IFC viewer using [IFC-Lite](https://github.com/LTplus-AG/ifc-lite).
|
|
|
249
249
|
## Quick Start
|
|
250
250
|
|
|
251
251
|
\`\`\`bash
|
|
252
|
-
npm
|
|
252
|
+
npm ci
|
|
253
253
|
npm run dev
|
|
254
254
|
\`\`\`
|
|
255
255
|
|
|
256
256
|
Open the URL Vite prints (http://localhost:5173 by default) and pick an IFC file.
|
|
257
257
|
|
|
258
|
-
No IFC file handy? [AC20-FZK-Haus.ifc](https://github.com/LTplus-AG/ifc-lite/releases/download/fixtures-v1/
|
|
258
|
+
No IFC file handy? [AC20-FZK-Haus.ifc](https://github.com/LTplus-AG/ifc-lite/releases/download/fixtures-v1/d42ab0dee01173106c16cd823b4614efe188fa15dd65195d95b9879bec59acc5) is a small public test model.
|
|
259
259
|
|
|
260
260
|
## Learn More
|
|
261
261
|
|
package/dist/templates/basic.js
CHANGED
|
@@ -3,16 +3,16 @@
|
|
|
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 { getPinnedPackageVersion } from '../utils/config-fixers.js';
|
|
7
7
|
import { writeGitignore } from '../utils/gitignore.js';
|
|
8
8
|
/**
|
|
9
9
|
* Scaffold a minimal TypeScript project for parsing IFC files.
|
|
10
10
|
*/
|
|
11
11
|
export function createBasicTemplate(targetDir, projectName) {
|
|
12
|
-
const parserVersion =
|
|
12
|
+
const parserVersion = getPinnedPackageVersion('@ifc-lite/parser');
|
|
13
13
|
// `store.entityIndex.byType` is keyed by the raw STEP spelling, and the
|
|
14
14
|
// PascalCase lookup table for it lives in @ifc-lite/data.
|
|
15
|
-
const dataVersion =
|
|
15
|
+
const dataVersion = getPinnedPackageVersion('@ifc-lite/data');
|
|
16
16
|
// package.json
|
|
17
17
|
writeFileSync(join(targetDir, 'package.json'), JSON.stringify({
|
|
18
18
|
name: projectName,
|
|
@@ -123,7 +123,7 @@ IFC parser project using [IFC-Lite](https://github.com/LTplus-AG/ifc-lite).
|
|
|
123
123
|
## Quick Start
|
|
124
124
|
|
|
125
125
|
\`\`\`bash
|
|
126
|
-
npm
|
|
126
|
+
npm ci
|
|
127
127
|
npm run parse ./your-model.ifc
|
|
128
128
|
\`\`\`
|
|
129
129
|
|
package/dist/templates/react.js
CHANGED
|
@@ -3,14 +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 { getPinnedPackageVersion, MODULE_WORKER_CONFIG } from '../utils/config-fixers.js';
|
|
7
7
|
import { writeGitignore } from '../utils/gitignore.js';
|
|
8
8
|
/**
|
|
9
9
|
* Scaffold a standalone React + Vite WebGPU viewer.
|
|
10
10
|
*/
|
|
11
11
|
export function createReactTemplate(targetDir, projectName) {
|
|
12
|
-
const geometryVersion =
|
|
13
|
-
const rendererVersion =
|
|
12
|
+
const geometryVersion = getPinnedPackageVersion('@ifc-lite/geometry');
|
|
13
|
+
const rendererVersion = getPinnedPackageVersion('@ifc-lite/renderer');
|
|
14
14
|
writeFileSync(join(targetDir, 'package.json'), JSON.stringify({
|
|
15
15
|
name: projectName,
|
|
16
16
|
version: '0.1.0',
|
|
@@ -63,7 +63,7 @@ const isolationHeaders = {
|
|
|
63
63
|
};
|
|
64
64
|
|
|
65
65
|
export default defineConfig({
|
|
66
|
-
plugins: [react()],
|
|
66
|
+
${MODULE_WORKER_CONFIG} plugins: [react()],
|
|
67
67
|
optimizeDeps: {
|
|
68
68
|
exclude: ['@ifc-lite/wasm'],
|
|
69
69
|
},
|
|
@@ -556,7 +556,7 @@ React + Vite IFC viewer using [IFC-Lite](https://github.com/LTplus-AG/ifc-lite).
|
|
|
556
556
|
## Quick Start
|
|
557
557
|
|
|
558
558
|
\`\`\`bash
|
|
559
|
-
npm
|
|
559
|
+
npm ci
|
|
560
560
|
npm run dev
|
|
561
561
|
\`\`\`
|
|
562
562
|
|
|
@@ -3,14 +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 { getPinnedPackageVersion } 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 serverBinVersion =
|
|
13
|
-
const serverClientVersion =
|
|
12
|
+
const serverBinVersion = getPinnedPackageVersion('@ifc-lite/server-bin');
|
|
13
|
+
const serverClientVersion = getPinnedPackageVersion('@ifc-lite/server-client');
|
|
14
14
|
// package.json
|
|
15
15
|
writeFileSync(join(targetDir, 'package.json'), JSON.stringify({
|
|
16
16
|
name: projectName,
|
|
@@ -326,7 +326,7 @@ IFC processing server using native binary - no Docker required.
|
|
|
326
326
|
|
|
327
327
|
\`\`\`bash
|
|
328
328
|
# Install dependencies (downloads server binary automatically)
|
|
329
|
-
npm
|
|
329
|
+
npm ci
|
|
330
330
|
|
|
331
331
|
# Start the server
|
|
332
332
|
npm run server:start
|
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 { getPinnedPackageVersion } 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 serverClientVersion =
|
|
11
|
+
const serverClientVersion = getPinnedPackageVersion('@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
|
|
@@ -484,7 +484,7 @@ IFC processing server using [IFC-Lite](https://github.com/LTplus-AG/ifc-lite).
|
|
|
484
484
|
docker compose up -d
|
|
485
485
|
|
|
486
486
|
# 2. Install client dependencies
|
|
487
|
-
npm
|
|
487
|
+
npm ci
|
|
488
488
|
|
|
489
489
|
# 3. Run the example (replace with your IFC file)
|
|
490
490
|
npm run example ./your-model.ifc
|
|
@@ -3,14 +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 { getPinnedPackageVersion, MODULE_WORKER_CONFIG } from '../utils/config-fixers.js';
|
|
7
7
|
import { writeGitignore } from '../utils/gitignore.js';
|
|
8
8
|
/**
|
|
9
9
|
* Scaffold a Three.js IFC viewer project using @ifc-lite/geometry.
|
|
10
10
|
* No WebGPU required — renders via Three.js WebGLRenderer.
|
|
11
11
|
*/
|
|
12
12
|
export function createThreejsTemplate(targetDir, projectName) {
|
|
13
|
-
const geometryVersion =
|
|
13
|
+
const geometryVersion = getPinnedPackageVersion('@ifc-lite/geometry');
|
|
14
14
|
// package.json
|
|
15
15
|
writeFileSync(join(targetDir, 'package.json'), JSON.stringify({
|
|
16
16
|
name: projectName,
|
|
@@ -50,12 +50,7 @@ export function createThreejsTemplate(targetDir, projectName) {
|
|
|
50
50
|
writeFileSync(join(targetDir, 'vite.config.ts'), `import { defineConfig } from 'vite';
|
|
51
51
|
|
|
52
52
|
export default defineConfig({
|
|
53
|
-
|
|
54
|
-
// production build never trips over Rollup's IIFE worker default.
|
|
55
|
-
worker: {
|
|
56
|
-
format: 'es',
|
|
57
|
-
},
|
|
58
|
-
optimizeDeps: {
|
|
53
|
+
${MODULE_WORKER_CONFIG} optimizeDeps: {
|
|
59
54
|
exclude: ['@ifc-lite/wasm'],
|
|
60
55
|
},
|
|
61
56
|
server: {
|
|
@@ -263,13 +258,13 @@ Three.js IFC viewer using [IFC-Lite](https://github.com/LTplus-AG/ifc-lite).
|
|
|
263
258
|
## Quick Start
|
|
264
259
|
|
|
265
260
|
\`\`\`bash
|
|
266
|
-
npm
|
|
261
|
+
npm ci
|
|
267
262
|
npm run dev
|
|
268
263
|
\`\`\`
|
|
269
264
|
|
|
270
265
|
Open the URL Vite prints (http://localhost:5173 by default) and pick an IFC file.
|
|
271
266
|
|
|
272
|
-
No IFC file handy? [AC20-FZK-Haus.ifc](https://github.com/LTplus-AG/ifc-lite/releases/download/fixtures-v1/
|
|
267
|
+
No IFC file handy? [AC20-FZK-Haus.ifc](https://github.com/LTplus-AG/ifc-lite/releases/download/fixtures-v1/d42ab0dee01173106c16cd823b4614efe188fa15dd65195d95b9879bec59acc5) is a small public test model.
|
|
273
268
|
|
|
274
269
|
## Learn More
|
|
275
270
|
|
|
@@ -5,6 +5,9 @@
|
|
|
5
5
|
* placeholder versions.
|
|
6
6
|
*/
|
|
7
7
|
export declare function getPackageVersion(packageName: string): string;
|
|
8
|
+
/** Pin the selected, inspected release so npm cannot reselect a rejected newer one. */
|
|
9
|
+
export declare function getPinnedPackageVersion(packageName: string): string;
|
|
10
|
+
export declare const MODULE_WORKER_CONFIG = " // Published geometry workers are ES modules and may split chunks.\n worker: { format: 'es' },\n";
|
|
8
11
|
/**
|
|
9
12
|
* Rewrite the viewer's package.json so it works as a standalone project:
|
|
10
13
|
* - Set the project name
|
|
@@ -4,17 +4,18 @@
|
|
|
4
4
|
import { existsSync, readFileSync, writeFileSync, rmSync } from 'fs';
|
|
5
5
|
import { join } from 'path';
|
|
6
6
|
import { execFileSync, spawnSync } from 'child_process';
|
|
7
|
+
import { resolveNpmCommand, isMissingNpmArtifact } from './npm-command.js';
|
|
8
|
+
import { hasWorkerFiles, publishedWorkerAssetsPresent } from './published-worker-assets.js';
|
|
7
9
|
/** Cache of npm-resolved versions to avoid redundant registry queries. */
|
|
8
10
|
const versionCache = new Map();
|
|
9
11
|
const publishedVersionsCache = new Map();
|
|
12
|
+
const dependenciesCache = new Map();
|
|
13
|
+
const artifactCache = new Map();
|
|
10
14
|
const VALID_PACKAGE_NAME = /^(?:@[\w.-]+\/)?[\w.-]+$/;
|
|
11
15
|
const NPM_TIMEOUT_MS = 30000;
|
|
12
16
|
const MAX_VERSION_CANDIDATES = 10;
|
|
13
|
-
/** npm's own codes for "the registry does not have that artifact". */
|
|
14
|
-
const MISSING_ARTIFACT_CODE = /\bE(404|410)\b/;
|
|
15
17
|
function readJsonFromNpm(args) {
|
|
16
|
-
const command
|
|
17
|
-
const commandArgs = process.platform === 'win32' ? ['/d', '/s', '/c', 'npm', ...args] : args;
|
|
18
|
+
const { command, args: commandArgs } = resolveNpmCommand(args);
|
|
18
19
|
const result = execFileSync(command, commandArgs, {
|
|
19
20
|
stdio: 'pipe',
|
|
20
21
|
timeout: NPM_TIMEOUT_MS,
|
|
@@ -39,11 +40,17 @@ function extractPinnedVersion(range) {
|
|
|
39
40
|
return match ? match[0] : null;
|
|
40
41
|
}
|
|
41
42
|
function getVersionDependencies(packageName, version) {
|
|
42
|
-
const
|
|
43
|
+
const key = `${packageName}@${version}`;
|
|
44
|
+
const cached = dependenciesCache.get(key);
|
|
45
|
+
if (cached)
|
|
46
|
+
return cached;
|
|
47
|
+
const json = readJsonFromNpm(['view', key, 'dependencies', '--json']);
|
|
43
48
|
if (!json || typeof json !== 'object' || Array.isArray(json)) {
|
|
44
49
|
return {};
|
|
45
50
|
}
|
|
46
|
-
|
|
51
|
+
const dependencies = json;
|
|
52
|
+
dependenciesCache.set(key, dependencies);
|
|
53
|
+
return dependencies;
|
|
47
54
|
}
|
|
48
55
|
/**
|
|
49
56
|
* Is the version's own tarball actually downloadable?
|
|
@@ -61,28 +68,49 @@ function getVersionDependencies(packageName, version) {
|
|
|
61
68
|
* older version, so anything else is treated as "cannot tell, assume fine".
|
|
62
69
|
*/
|
|
63
70
|
function isTarballFetchable(packageName, version) {
|
|
71
|
+
const key = `${packageName}@${version}`;
|
|
72
|
+
const cached = artifactCache.get(key);
|
|
73
|
+
if (cached !== undefined)
|
|
74
|
+
return cached;
|
|
64
75
|
const args = ['pack', `${packageName}@${version}`, '--dry-run', '--json'];
|
|
65
|
-
const command
|
|
66
|
-
const commandArgs = process.platform === 'win32' ? ['/d', '/s', '/c', 'npm', ...args] : args;
|
|
76
|
+
const { command, args: commandArgs } = resolveNpmCommand(args);
|
|
67
77
|
const result = spawnSync(command, commandArgs, { encoding: 'utf-8', timeout: NPM_TIMEOUT_MS });
|
|
68
|
-
if (result.status === 0)
|
|
69
|
-
|
|
78
|
+
if (result.status === 0) {
|
|
79
|
+
const metadata = JSON.parse(result.stdout || '[]');
|
|
80
|
+
const valid = !hasWorkerFiles(metadata) || publishedWorkerAssetsPresent(packageName, version);
|
|
81
|
+
artifactCache.set(key, valid);
|
|
82
|
+
return valid;
|
|
83
|
+
}
|
|
70
84
|
const output = `${result.stdout ?? ''}${result.stderr ?? ''}`;
|
|
71
|
-
return !
|
|
85
|
+
return !isMissingNpmArtifact(output);
|
|
72
86
|
}
|
|
73
87
|
function isInstallablePublishedVersion(packageName, version) {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
const
|
|
79
|
-
|
|
88
|
+
// Iterative: registry dependency graphs can contain cycles or shared subgraphs.
|
|
89
|
+
const queue = [[packageName, version]];
|
|
90
|
+
const seen = new Set();
|
|
91
|
+
for (let index = 0; index < queue.length; index++) {
|
|
92
|
+
const [name, candidate] = queue[index];
|
|
93
|
+
const key = `${name}@${candidate}`;
|
|
94
|
+
if (seen.has(key))
|
|
80
95
|
continue;
|
|
81
|
-
|
|
82
|
-
|
|
96
|
+
seen.add(key);
|
|
97
|
+
if (seen.size > 128)
|
|
98
|
+
throw new Error('Scaffold dependency validation exceeds 128 package versions');
|
|
99
|
+
const dependencies = getVersionDependencies(name, candidate);
|
|
100
|
+
for (const [dependencyName, dependencyRange] of Object.entries(dependencies)) {
|
|
101
|
+
if (!dependencyName.startsWith('@ifc-lite/'))
|
|
102
|
+
continue;
|
|
103
|
+
const pinnedVersion = extractPinnedVersion(dependencyRange);
|
|
104
|
+
if (!pinnedVersion)
|
|
105
|
+
continue;
|
|
106
|
+
if (!getPublishedVersions(dependencyName).has(pinnedVersion))
|
|
107
|
+
return false;
|
|
108
|
+
queue.push([dependencyName, pinnedVersion]);
|
|
83
109
|
}
|
|
110
|
+
if (!isTarballFetchable(name, candidate))
|
|
111
|
+
return false;
|
|
84
112
|
}
|
|
85
|
-
return
|
|
113
|
+
return true;
|
|
86
114
|
}
|
|
87
115
|
/**
|
|
88
116
|
* Fetch the latest installable published version of a specific npm package.
|
|
@@ -110,6 +138,11 @@ export function getPackageVersion(packageName) {
|
|
|
110
138
|
'Check your npm registry access and try again.', { cause });
|
|
111
139
|
}
|
|
112
140
|
}
|
|
141
|
+
/** Pin the selected, inspected release so npm cannot reselect a rejected newer one. */
|
|
142
|
+
export function getPinnedPackageVersion(packageName) {
|
|
143
|
+
return getPackageVersion(packageName).replace(/^\^/, '');
|
|
144
|
+
}
|
|
145
|
+
export const MODULE_WORKER_CONFIG = ` // Published geometry workers are ES modules and may split chunks.\n worker: { format: 'es' },\n`;
|
|
113
146
|
/**
|
|
114
147
|
* Rewrite the viewer's package.json so it works as a standalone project:
|
|
115
148
|
* - Set the project name
|
|
@@ -0,0 +1,23 @@
|
|
|
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 { execFileSync } from 'node:child_process';
|
|
5
|
+
import { readFileSync } from 'node:fs';
|
|
6
|
+
import { join } from 'node:path';
|
|
7
|
+
import { resolveNpmCommand } from './npm-command.js';
|
|
8
|
+
/** Resolve the entire dependency graph together, without running install scripts. */
|
|
9
|
+
export function createDependencyLock(targetDir) {
|
|
10
|
+
const { command, args } = resolveNpmCommand(['install', '--package-lock-only', '--ignore-scripts',
|
|
11
|
+
'--strict-peer-deps', '--no-audit', '--no-fund']);
|
|
12
|
+
try {
|
|
13
|
+
execFileSync(command, args, { cwd: targetDir, stdio: 'pipe', timeout: 120000 });
|
|
14
|
+
const lock = JSON.parse(readFileSync(join(targetDir, 'package-lock.json'), 'utf8'));
|
|
15
|
+
if (!lock || typeof lock !== 'object' || !('packages' in lock) ||
|
|
16
|
+
!lock.packages || typeof lock.packages !== 'object' || !('' in lock.packages)) {
|
|
17
|
+
throw new Error('npm did not produce a dependency lock with a root package');
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
catch (cause) {
|
|
21
|
+
throw new Error('Could not resolve a compatible scaffold dependency set. Check npm registry access and peer requirements.', { cause });
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** Keep npm's Windows command-shim invocation consistent across scaffold steps. */
|
|
2
|
+
export declare function resolveNpmCommand(args: string[]): {
|
|
3
|
+
command: string;
|
|
4
|
+
args: string[];
|
|
5
|
+
};
|
|
6
|
+
/** Explicit npm registry refusal, distinct from an inconclusive transport error. */
|
|
7
|
+
export declare function isMissingNpmArtifact(output: string): boolean;
|
|
@@ -0,0 +1,13 @@
|
|
|
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
|
+
/** Keep npm's Windows command-shim invocation consistent across scaffold steps. */
|
|
5
|
+
export function resolveNpmCommand(args) {
|
|
6
|
+
return process.platform === 'win32'
|
|
7
|
+
? { command: process.env.ComSpec ?? 'cmd.exe', args: ['/d', '/s', '/c', 'npm', ...args] }
|
|
8
|
+
: { command: 'npm', args };
|
|
9
|
+
}
|
|
10
|
+
/** Explicit npm registry refusal, distinct from an inconclusive transport error. */
|
|
11
|
+
export function isMissingNpmArtifact(output) {
|
|
12
|
+
return /\bE(404|410)\b/.test(output);
|
|
13
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** A tarball's relative literal worker URLs must resolve to files it actually ships. */
|
|
2
|
+
export declare function missingWorkerAssets(archive: string): Array<{
|
|
3
|
+
file: string;
|
|
4
|
+
specifier: string;
|
|
5
|
+
}>;
|
|
6
|
+
export declare function publishedWorkerAssetsPresent(packageName: string, version: string): boolean;
|
|
7
|
+
export declare function hasWorkerFiles(metadata: unknown): boolean;
|
|
@@ -0,0 +1,101 @@
|
|
|
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 { execFileSync } from 'node:child_process';
|
|
5
|
+
import { mkdtempSync, rmSync } from 'node:fs';
|
|
6
|
+
import { tmpdir } from 'node:os';
|
|
7
|
+
import { basename, join, posix } from 'node:path';
|
|
8
|
+
import { list } from 'tar';
|
|
9
|
+
import { tokenizer } from 'acorn';
|
|
10
|
+
import { resolveNpmCommand, isMissingNpmArtifact } from './npm-command.js';
|
|
11
|
+
const valueOf = (token) => 'value' in token ? token.value : undefined;
|
|
12
|
+
function literalWorkerUrls(source) {
|
|
13
|
+
if (!source.includes('worker.'))
|
|
14
|
+
return [];
|
|
15
|
+
const recent = [];
|
|
16
|
+
const found = [];
|
|
17
|
+
for (const token of tokenizer(source, { ecmaVersion: 'latest', sourceType: 'module', allowHashBang: true })) {
|
|
18
|
+
recent.push(token);
|
|
19
|
+
if (recent.length > 11)
|
|
20
|
+
recent.shift();
|
|
21
|
+
if (recent.length !== 11)
|
|
22
|
+
continue;
|
|
23
|
+
const labels = recent.map(item => item.type.label).join(' ');
|
|
24
|
+
if (labels !== 'new name ( string , import . name . name )' ||
|
|
25
|
+
valueOf(recent[1]) !== 'URL' || valueOf(recent[7]) !== 'meta' || valueOf(recent[9]) !== 'url')
|
|
26
|
+
continue;
|
|
27
|
+
const specifier = valueOf(recent[3]);
|
|
28
|
+
if (typeof specifier === 'string' && specifier.startsWith('.') && /worker\.[cm]?[jt]s(?:[?#]|$)/.test(specifier))
|
|
29
|
+
found.push(specifier);
|
|
30
|
+
}
|
|
31
|
+
return found;
|
|
32
|
+
}
|
|
33
|
+
/** A tarball's relative literal worker URLs must resolve to files it actually ships. */
|
|
34
|
+
export function missingWorkerAssets(archive) {
|
|
35
|
+
const files = new Set();
|
|
36
|
+
const references = [];
|
|
37
|
+
list({ file: archive, sync: true, strict: true, onReadEntry(entry) {
|
|
38
|
+
if (entry.type !== 'File') {
|
|
39
|
+
entry.resume();
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
const file = posix.normalize(entry.path);
|
|
43
|
+
files.add(file);
|
|
44
|
+
if (!/\.[cm]?js$/.test(file)) {
|
|
45
|
+
entry.resume();
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
const chunks = [];
|
|
49
|
+
entry.on('data', (chunk) => { chunks.push(chunk); });
|
|
50
|
+
entry.on('end', () => {
|
|
51
|
+
for (const specifier of literalWorkerUrls(Buffer.concat(chunks).toString('utf8')))
|
|
52
|
+
references.push({ file, specifier });
|
|
53
|
+
});
|
|
54
|
+
} });
|
|
55
|
+
return references.filter(({ file, specifier }) => {
|
|
56
|
+
const base = new URL(file.split('/').map(encodeURIComponent).join('/'), 'https://artifact.invalid/');
|
|
57
|
+
const target = decodeURIComponent(new URL(specifier, base).pathname).slice(1);
|
|
58
|
+
return !target.startsWith('package/') || !files.has(target);
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
export function publishedWorkerAssetsPresent(packageName, version) {
|
|
62
|
+
const scratch = mkdtempSync(join(tmpdir(), 'ifclite-worker-artifact-'));
|
|
63
|
+
try {
|
|
64
|
+
const { command, args } = resolveNpmCommand(['pack', `${packageName}@${version}`, '--json', '--pack-destination', scratch]);
|
|
65
|
+
let output;
|
|
66
|
+
try {
|
|
67
|
+
output = execFileSync(command, args, { encoding: 'utf8', stdio: 'pipe', timeout: 30000 });
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
const details = error && typeof error === 'object'
|
|
71
|
+
? ['stdout', 'stderr', 'code'].map(key => Reflect.get(error, key)).map(String).join(' ') : String(error);
|
|
72
|
+
if (isMissingNpmArtifact(details))
|
|
73
|
+
return false;
|
|
74
|
+
// Preserve the version probe's existing policy: an inconclusive network
|
|
75
|
+
// failure must not silently downgrade the selected package (PR #7660).
|
|
76
|
+
console.warn(`Could not inspect worker assets for ${packageName}@${version}; ` +
|
|
77
|
+
'npm did not report a missing artifact, so the version candidate is retained.');
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
const result = JSON.parse(output);
|
|
81
|
+
const first = Array.isArray(result) ? result[0] : undefined;
|
|
82
|
+
if (!first || typeof first !== 'object' || !('filename' in first) || typeof first.filename !== 'string' ||
|
|
83
|
+
basename(first.filename) !== first.filename)
|
|
84
|
+
throw new Error('npm did not identify a packed artifact');
|
|
85
|
+
const missing = missingWorkerAssets(join(scratch, first.filename));
|
|
86
|
+
if (missing.length > 0)
|
|
87
|
+
console.warn(`Skipping ${packageName}@${version}: worker assets are absent from its tarball: ` +
|
|
88
|
+
missing.map(item => `${item.file}: ${item.specifier}`).join(', '));
|
|
89
|
+
return missing.length === 0;
|
|
90
|
+
}
|
|
91
|
+
finally {
|
|
92
|
+
rmSync(scratch, { recursive: true, force: true });
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
export function hasWorkerFiles(metadata) {
|
|
96
|
+
if (!Array.isArray(metadata))
|
|
97
|
+
return false;
|
|
98
|
+
return metadata.some((item) => item && typeof item === 'object' && 'files' in item && Array.isArray(item.files) &&
|
|
99
|
+
item.files.some((file) => file && typeof file === 'object' && 'path' in file && typeof file.path === 'string' &&
|
|
100
|
+
/worker\.[cm]?[jt]s$/.test(file.path)));
|
|
101
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-ifc-lite",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.16.0",
|
|
4
4
|
"description": "Create IFC-Lite projects with one command",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -31,12 +31,16 @@
|
|
|
31
31
|
"node": ">=18.0.0"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
|
-
"@types/node": "^26.6.
|
|
34
|
+
"@types/node": "^26.6.4",
|
|
35
35
|
"typescript": "^5.3.0",
|
|
36
|
-
"vitest": "^5.0.
|
|
36
|
+
"vitest": "^5.0.3"
|
|
37
37
|
},
|
|
38
38
|
"homepage": "https://ifclite.dev/docs/",
|
|
39
39
|
"bugs": "https://github.com/LTplus-AG/ifc-lite/issues",
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"acorn": "^8.19.0",
|
|
42
|
+
"tar": "^7.5.21"
|
|
43
|
+
},
|
|
40
44
|
"scripts": {
|
|
41
45
|
"build": "pnpm exec tsc",
|
|
42
46
|
"typecheck": "node ../../scripts/typecheck-tests.mjs",
|