create-ifc-lite 1.15.1 → 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # create-ifc-lite
2
2
 
3
- Scaffolds an IFClite project in seconds. One command, working code.
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
- That's it — no install step. The scaffolder picks `basic` if you don't pass a template.
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 install && npm run parse ./model.ifc
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 install && npm run dev
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 install && npm run example ./your-model.ifc`);
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 install`);
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 install`);
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 { getPackageVersion } from '../utils/config-fixers.js';
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 = getPackageVersion('@ifc-lite/geometry');
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,7 +249,7 @@ 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 install
252
+ npm ci
253
253
  npm run dev
254
254
  \`\`\`
255
255
 
@@ -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 { getPackageVersion } from '../utils/config-fixers.js';
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 = getPackageVersion('@ifc-lite/parser');
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 = getPackageVersion('@ifc-lite/data');
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 install
126
+ npm ci
127
127
  npm run parse ./your-model.ifc
128
128
  \`\`\`
129
129
 
@@ -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 { getPackageVersion } from '../utils/config-fixers.js';
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 = getPackageVersion('@ifc-lite/geometry');
13
- const rendererVersion = getPackageVersion('@ifc-lite/renderer');
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 install
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 { getPackageVersion } from '../utils/config-fixers.js';
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 = getPackageVersion('@ifc-lite/server-bin');
13
- const serverClientVersion = getPackageVersion('@ifc-lite/server-client');
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 install
329
+ npm ci
330
330
 
331
331
  # Start the server
332
332
  npm run server:start
@@ -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 { getPackageVersion } from '../utils/config-fixers.js';
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 = getPackageVersion('@ifc-lite/server-client');
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 install
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 { getPackageVersion } from '../utils/config-fixers.js';
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 = getPackageVersion('@ifc-lite/geometry');
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
- // @ifc-lite ships ES-module geometry workers; emit them as ESM so a
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,7 +258,7 @@ 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 install
261
+ npm ci
267
262
  npm run dev
268
263
  \`\`\`
269
264
 
@@ -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 = process.platform === 'win32' ? process.env.ComSpec ?? 'cmd.exe' : 'npm';
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 json = readJsonFromNpm(['view', `${packageName}@${version}`, 'dependencies', '--json']);
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
- return json;
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 = process.platform === 'win32' ? process.env.ComSpec ?? 'cmd.exe' : 'npm';
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
- return true;
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 !MISSING_ARTIFACT_CODE.test(output);
85
+ return !isMissingNpmArtifact(output);
72
86
  }
73
87
  function isInstallablePublishedVersion(packageName, version) {
74
- const dependencies = getVersionDependencies(packageName, version);
75
- for (const [dependencyName, dependencyRange] of Object.entries(dependencies)) {
76
- if (!dependencyName.startsWith('@ifc-lite/'))
77
- continue;
78
- const pinnedVersion = extractPinnedVersion(dependencyRange);
79
- if (!pinnedVersion)
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
- if (!getPublishedVersions(dependencyName).has(pinnedVersion)) {
82
- return false;
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 isTarballFetchable(packageName, version);
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,2 @@
1
+ /** Resolve the entire dependency graph together, without running install scripts. */
2
+ export declare function createDependencyLock(targetDir: string): void;
@@ -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.15.1",
3
+ "version": "1.16.0",
4
4
  "description": "Create IFC-Lite projects with one command",
5
5
  "type": "module",
6
6
  "bin": {
@@ -37,6 +37,10 @@
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",