@playcraft/cli 0.1.39-prod → 0.1.41-prod

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.
@@ -3,9 +3,9 @@ import { writeFileSync, mkdirSync } from 'fs';
3
3
  import { dirname, parse, join } from 'path';
4
4
  import sharp from 'sharp';
5
5
  import { resolveImageBackgroundRequirement, validateImageDeliveryOptions } from '@playcraft/common/prefab';
6
- import { readImageAssetContext } from '../utils/image-asset-context.js';
6
+ import { assertImageAssetOutputPaths, readImageAssetContext } from '../utils/image-asset-context.js';
7
7
  import { AgentApiClient } from '../utils/agent-api-client.js';
8
- import { MAX_REFERENCE_IMAGES, collectReferenceImagePaths, collectReferenceImagePayloads, sniffImageExtension, extensionForImageMime, resolveImageOutputPath, handleError, raceWithTimeout, } from './tools-utils.js';
8
+ import { MAX_REFERENCE_IMAGES, collectReferenceImagePaths, parseImageTargetFieldPath, collectReferenceImagePayloads, sniffImageExtension, extensionForImageMime, resolveImageOutputPath, handleError, raceWithTimeout, } from './tools-utils.js';
9
9
  function isMasterCompositeStripRequest(opts) {
10
10
  if (opts.aspectRatio === '45:16')
11
11
  return true;
@@ -154,6 +154,7 @@ export function registerGenerationCommands(tools) {
154
154
  .option('--aspect-ratio <ratio>', '宽高比 (1:1|45:16|16:9|9:16|3:4|4:3|2:3|3:2|21:9)', '1:1')
155
155
  .requiredOption('--output <path>', '保存路径(--count>1 时自动追加 _1/_2 等后缀)')
156
156
  .option('--target-asset <path>', 'Existing asset to replace; read its schema references and delivery constraints')
157
+ .option('--target-field-path <path>', 'Target image field as an array of keys, e.g. ["endUI","playButton"]', parseImageTargetFieldPath)
157
158
  .option('--no-target-reference', 'Generate without the existing asset reference only when explicitly requested')
158
159
  .option('--output-format <format>', 'Final format: png, webp or jpeg')
159
160
  .option('--fit-mode <mode>', 'Canvas fitting: contain, cover or fill')
@@ -176,9 +177,16 @@ export function registerGenerationCommands(tools) {
176
177
  throw new Error('Conflicting background options. Use --background transparent or --background opaque; no image request was sent.');
177
178
  }
178
179
  opts = { ...opts, removeBackground: explicitBackground };
180
+ if (opts.targetFieldPath && !opts.targetAsset) {
181
+ throw new Error('--target-field-path requires --target-asset; no image request was sent.');
182
+ }
179
183
  const explicitOutputFormat = opts.outputFormat;
184
+ const { dir: outputDir, name: outputName, ext: outputExt } = parse(opts.output);
185
+ const outputCandidates = [opts.output, ...Array.from({ length: opts.count > 1 ? Math.min(opts.count, 10) : 0 }, (_, i) => join(outputDir, `${outputName}_${i + 1}${outputExt}`))];
180
186
  const assetContext = opts.targetAsset ? await readImageAssetContext(opts.targetAsset, process.cwd(), {
181
187
  referenceImagePaths: opts.referenceImage, useTargetReference: opts.targetReference,
188
+ ...(opts.targetFieldPath ? { targetFieldPath: opts.targetFieldPath } : {}),
189
+ outputPaths: outputCandidates,
182
190
  prompt: opts.prompt, removeBackground: opts.removeBackground,
183
191
  }) : undefined;
184
192
  opts = { ...opts,
@@ -343,6 +351,8 @@ export function registerGenerationCommands(tools) {
343
351
  console.log(`↑ Letterbox ${generatedWidth}×${generatedHeight} → ${stripTarget.width}×${stripTarget.height}(等比 + 白边,不拉伸)`);
344
352
  }
345
353
  }
354
+ if (assetContext)
355
+ assertImageAssetOutputPaths(assetContext, [resolvedPath]);
346
356
  mkdirSync(dirname(resolvedPath), { recursive: true });
347
357
  writeFileSync(resolvedPath, imgBuf);
348
358
  let actualWidth = null;
@@ -1,9 +1,12 @@
1
1
  import { readImageAssetContext } from '../utils/image-asset-context.js';
2
- import { collectReferenceImagePaths, handleError } from './tools-utils.js';
2
+ import { collectReferenceImagePaths, handleError, parseImageTargetFieldPath } from './tools-utils.js';
3
3
  export function registerImageContextCommand(tools) {
4
4
  tools.command('image-context')
5
5
  .description('Read image asset reference and delivery constraints locally; does not generate an image')
6
6
  .requiredOption('--asset-path <path>', 'Existing image asset path relative to the project')
7
+ .option('--target-field-path <path>', 'Target image field as an array of keys, e.g. ["endUI","playButton"]', parseImageTargetFieldPath)
8
+ .option('--output-path <path>', 'Intended output path; checks shared asset protection before generation', collectReferenceImagePaths, [])
9
+ .option('--allowed-output-root <path>', 'Additional output directory authorized by the caller; does not allow external references', collectReferenceImagePaths, [])
7
10
  .option('--reference-image <path>', 'Explicit local reference; may be repeated', collectReferenceImagePaths, [])
8
11
  .option('--no-target-reference', 'Do not use the target schema or current asset as a reference')
9
12
  .option('--prompt <text>', 'Generation request used to resolve background intent')
@@ -13,6 +16,9 @@ export function registerImageContextCommand(tools) {
13
16
  try {
14
17
  console.log(JSON.stringify(await readImageAssetContext(opts.assetPath, process.cwd(), {
15
18
  referenceImagePaths: opts.referenceImage, useTargetReference: opts.targetReference,
19
+ ...(opts.targetFieldPath ? { targetFieldPath: opts.targetFieldPath } : {}),
20
+ outputPaths: opts.outputPath,
21
+ allowedOutputRoots: opts.allowedOutputRoot,
16
22
  prompt: opts.prompt, removeBackground: opts.removeBackground,
17
23
  })));
18
24
  }
@@ -1,8 +1,20 @@
1
1
  import { writeFileSync, mkdirSync, readFileSync } from 'fs';
2
2
  import { join, parse } from 'path';
3
3
  import { tmpdir } from 'os';
4
+ import { InvalidArgumentError } from 'commander';
4
5
  export const TMP_DIR = join(tmpdir(), 'playcraft');
5
6
  export const MAX_REFERENCE_IMAGES = 8;
7
+ export function parseImageTargetFieldPath(value) {
8
+ let parsed;
9
+ try {
10
+ parsed = JSON.parse(value);
11
+ }
12
+ catch { /* Report the expected shape below. */ }
13
+ if (!Array.isArray(parsed) || !parsed.length || parsed.some(key => typeof key !== 'string' || !key.length)) {
14
+ throw new InvalidArgumentError('Expected an array of field names, e.g. ["endUI","playButton"].');
15
+ }
16
+ return parsed;
17
+ }
6
18
  export function ensureTmpDir() {
7
19
  mkdirSync(TMP_DIR, { recursive: true });
8
20
  }
@@ -1,27 +1,87 @@
1
- import { existsSync, readFileSync, realpathSync } from 'node:fs';
1
+ import { existsSync, readFileSync, realpathSync, statSync } from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import JSON5 from 'json5';
4
4
  import sharp from 'sharp';
5
5
  import { extractAssetFields, getByPath, resolveAiImageTargetConstraints, resolveImageBackgroundRequirement, THEME_SCHEMA_PATH, themeDataPath, } from '@playcraft/common/prefab';
6
+ function isWithinDirectory(root, file) {
7
+ const relative = path.relative(root, file);
8
+ return relative !== '..' && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative);
9
+ }
10
+ /** Resolve existing ancestors so a new workspace need not have been created yet. */
11
+ function realPathWithParents(file) {
12
+ let ancestor = file;
13
+ while (!existsSync(ancestor)) {
14
+ const parent = path.dirname(ancestor);
15
+ if (parent === ancestor)
16
+ throw new Error(`Image path has no existing ancestor: ${file}`);
17
+ ancestor = parent;
18
+ }
19
+ return path.resolve(realpathSync(ancestor), path.relative(ancestor, file));
20
+ }
21
+ /** Resolve aliases and existing parent symlinks, including for a new output file. */
22
+ function canonicalProjectPath(root, input) {
23
+ const mapped = input.startsWith('/project/') ? input.slice('/project/'.length) : input;
24
+ const file = path.resolve(root, mapped);
25
+ if (!isWithinDirectory(root, file)) {
26
+ throw new Error(`Image asset path is outside the project: ${input}`);
27
+ }
28
+ const real = realPathWithParents(file);
29
+ if (!isWithinDirectory(realPathWithParents(root), real)) {
30
+ throw new Error(`Image asset symlink is outside the project: ${input}`);
31
+ }
32
+ return real;
33
+ }
34
+ /** Check every extension a writer may choose before sending a generation request. */
35
+ export function assertImageAssetOutputPaths(context, outputPaths, projectDir = process.cwd(), allowedOutputRoots = []) {
36
+ if (!context.requiresSeparateOutput)
37
+ return;
38
+ const root = path.resolve(projectDir);
39
+ const target = canonicalProjectPath(root, context.targetAssetPath);
40
+ const targetStat = statSync(target);
41
+ const outputRoots = [root, ...allowedOutputRoots.map(outputRoot => path.resolve(root, outputRoot))];
42
+ for (const output of outputPaths) {
43
+ const mapped = output.startsWith('/project/') ? output.slice('/project/'.length) : output;
44
+ const { dir, name } = path.parse(mapped);
45
+ // The response encoding can change the requested extension.
46
+ const candidates = [mapped, ...['.png', '.webp', '.jpg', '.gif', '.bmp'].map(ext => path.join(dir, `${name}${ext}`))];
47
+ for (const candidate of candidates) {
48
+ const candidatePath = path.resolve(root, candidate);
49
+ const outputRoot = outputRoots.find(allowed => isWithinDirectory(allowed, candidatePath));
50
+ if (!outputRoot)
51
+ throw new Error(`Image output path is outside the project and allowed output roots: ${output}`);
52
+ const file = canonicalProjectPath(outputRoot, candidatePath);
53
+ const fileStat = existsSync(file) ? statSync(file) : undefined;
54
+ if (!path.relative(target, file) || (targetStat.ino !== 0 && fileStat?.ino === targetStat.ino && fileStat.dev === targetStat.dev)) {
55
+ throw new Error(`Cannot overwrite shared image asset ${context.targetAssetPath}: other fields have different requirements. Use a separate outputPath, then explicitly update the selected field reference; no shared asset was changed.`);
56
+ }
57
+ }
58
+ }
59
+ }
6
60
  /** Resolve only real files in this project, including legacy /project aliases. */
7
61
  function projectFile(root, input) {
8
62
  const mapped = input.startsWith('/project/') ? input.slice('/project/'.length) : input;
9
63
  const file = path.resolve(root, mapped);
10
- const prefix = path.resolve(root) + path.sep;
11
- if (!file.startsWith(prefix))
64
+ if (!isWithinDirectory(root, file))
12
65
  throw new Error(`Image asset path is outside the project: ${input}`);
13
66
  const real = realpathSync(file);
14
- if (!real.startsWith(realpathSync(root) + path.sep))
67
+ if (!isWithinDirectory(realpathSync(root), real))
15
68
  throw new Error(`Image asset symlink is outside the project: ${input}`);
16
69
  return file;
17
70
  }
18
71
  /** Read the same per-field constraints used by the asset image dialog. */
19
72
  export async function readImageAssetContext(assetPath, projectDir = process.cwd(), options = {}) {
73
+ const targetFieldPath = options.targetFieldPath;
74
+ if (targetFieldPath !== undefined && (!Array.isArray(targetFieldPath)
75
+ || !targetFieldPath.length || targetFieldPath.some(key => typeof key !== 'string' || !key.length))) {
76
+ throw new Error('targetFieldPath must be a non-empty array of non-empty field names.');
77
+ }
20
78
  const root = path.resolve(projectDir);
21
79
  const target = projectFile(root, assetPath);
80
+ const realTarget = realpathSync(target);
81
+ const targetStat = statSync(target);
22
82
  const relativeTarget = path.relative(root, target).replace(/\\/g, '/');
23
83
  const themeMatch = relativeTarget.match(/^src\/theme\/([^/]+)\/assets\//);
24
- let field;
84
+ let fields = [];
25
85
  const schemaFile = path.join(root, THEME_SCHEMA_PATH);
26
86
  if (themeMatch && existsSync(schemaFile)) {
27
87
  const schema = JSON5.parse(readFileSync(projectFile(root, THEME_SCHEMA_PATH), 'utf8'));
@@ -37,70 +97,132 @@ export async function readImageAssetContext(assetPath, projectDir = process.cwd(
37
97
  const value = typeof configured === 'string' ? configured : candidate.schema.default;
38
98
  if (typeof value !== 'string' || /^https?:\/\//i.test(value))
39
99
  return false;
40
- return path.resolve(themeRoot, value) === target;
100
+ const configuredPath = value.startsWith('/project/') ? path.resolve(root, value.slice('/project/'.length))
101
+ : path.resolve(themeRoot, value);
102
+ if (configuredPath === target)
103
+ return true;
104
+ try {
105
+ const candidateFile = projectFile(root, configuredPath);
106
+ if (!path.relative(realTarget, realpathSync(candidateFile)))
107
+ return true;
108
+ const candidateStat = statSync(candidateFile);
109
+ return targetStat.ino !== 0 && candidateStat.ino === targetStat.ino && candidateStat.dev === targetStat.dev;
110
+ }
111
+ catch {
112
+ // Other fields may reference missing files or paths outside this project.
113
+ return false;
114
+ }
41
115
  });
42
- if (matches.length > 1)
43
- throw new Error('Multiple image fields use this asset; specify an unambiguous target asset.');
44
- field = matches[0];
45
- if (!field)
116
+ if (!matches.length)
46
117
  throw new Error(`No image schema field matches ${relativeTarget}; check theme.data.json5 before generating.`);
118
+ fields = matches;
119
+ if (targetFieldPath && !fields.some(candidate => candidate.path.length === targetFieldPath.length
120
+ && candidate.path.every((key, index) => key === targetFieldPath[index]))) {
121
+ throw new Error(`targetFieldPath ${JSON.stringify(targetFieldPath)} does not reference ${relativeTarget}. Matching fields: ${matches.map(candidate => JSON.stringify(candidate.path)).join(', ')}.`);
122
+ }
123
+ }
124
+ else if (targetFieldPath) {
125
+ throw new Error('targetFieldPath requires an asset with a matching theme schema.');
47
126
  }
48
- const xp = field?.schema.xplatform;
49
- const ext = xp?.ext;
50
- // Schema references follow the dialog's root/theme conventions; explicit tool paths do not change.
51
- const schemaReferences = Array.isArray(ext?.reference_image)
52
- ? ext.reference_image.filter(ref => typeof ref === 'string' && ref.trim()).map(ref => {
53
- const raw = ref.trim().replace(/\\/g, '/');
54
- const normalized = raw.replace(/^\.\/+/, '').replace(/^\/+/, '').replace(/\/{2,}/g, '/');
55
- if (/^\/?(?:assets|src|public)\//.test(raw) || normalized.startsWith('src/'))
56
- return normalized;
57
- if (!themeMatch || path.isAbsolute(raw) || /^https?:\/\//i.test(raw))
58
- return raw;
59
- return path.posix.join('src/theme', themeMatch[1], normalized);
60
- }) : [];
61
- const referenceImagePaths = options.referenceImagePaths?.length ? options.referenceImagePaths
62
- : options.useTargetReference === false ? [] : schemaReferences.length ? schemaReferences : [relativeTarget];
63
- if (referenceImagePaths.length > 8)
127
+ const targetMeta = await sharp(target).metadata();
128
+ let targetHasTransparency;
129
+ const contextForField = async (field) => {
130
+ const xp = field?.schema.xplatform;
131
+ const ext = xp?.ext;
132
+ // Schema references follow the dialog's root/theme conventions; explicit tool paths do not change.
133
+ const schemaReferences = Array.isArray(ext?.reference_image)
134
+ ? ext.reference_image.filter(ref => typeof ref === 'string' && ref.trim()).map(ref => {
135
+ const raw = ref.trim().replace(/\\/g, '/');
136
+ const normalized = raw.replace(/^\.\/+/, '').replace(/^\/+/, '').replace(/\/{2,}/g, '/');
137
+ if (/^\/?(?:assets|src|public)\//.test(raw) || normalized.startsWith('src/'))
138
+ return normalized;
139
+ if (!themeMatch || path.isAbsolute(raw) || /^https?:\/\//i.test(raw))
140
+ return raw;
141
+ return path.posix.join('src/theme', themeMatch[1], normalized);
142
+ }) : [];
143
+ const references = options.referenceImagePaths?.length ? options.referenceImagePaths
144
+ : options.useTargetReference === false ? [] : schemaReferences.length ? schemaReferences : [relativeTarget];
145
+ const referenceImagePaths = references.map(ref => /^https?:\/\//i.test(ref) ? ref
146
+ : path.relative(realpathSync(root), canonicalProjectPath(root, ref)).replace(/\\/g, '/'));
147
+ const hasSchemaBackground = typeof ext?.remove_background === 'boolean';
148
+ if (!hasSchemaBackground && targetHasTransparency === undefined) {
149
+ targetHasTransparency = !!targetMeta.hasAlpha && (await sharp(target).stats()).channels.at(-1).min < 250;
150
+ }
151
+ const removeBackground = resolveImageBackgroundRequirement({
152
+ prompt: options.prompt ?? '',
153
+ schemaRemoveBackground: hasSchemaBackground ? ext.remove_background : undefined,
154
+ removeBackground: options.removeBackground,
155
+ fallbackRemoveBackground: targetHasTransparency ?? false,
156
+ });
157
+ const delivery = resolveAiImageTargetConstraints({
158
+ schemaResolution: ext?.resolution,
159
+ schemaMimeType: xp?.mime_type,
160
+ schemaFitMode: xp?.fit_mode ?? ext?.fit_mode,
161
+ schemaRemoveBackground: removeBackground,
162
+ schemaPaddingColor: ext?.padding_color,
163
+ assetPath: relativeTarget,
164
+ referenceWidth: targetMeta.width,
165
+ referenceHeight: targetMeta.height,
166
+ referenceFormat: targetMeta.format,
167
+ });
168
+ if (!['png', 'webp', 'jpeg'].includes(delivery.format))
169
+ throw new Error('Unsupported image delivery format; use a dedicated animation or vector workflow.');
170
+ return {
171
+ targetAssetPath: relativeTarget,
172
+ fieldPath: field?.path,
173
+ referenceImagePaths,
174
+ width: delivery.width, height: delivery.height,
175
+ aspectRatio: typeof ext?.ratio === 'string' ? ext.ratio : `${delivery.width}:${delivery.height}`,
176
+ outputFormat: (removeBackground && delivery.format === 'jpeg' && !delivery.fromSchemaMimeType
177
+ ? 'png' : delivery.format),
178
+ outputFormatFromSchema: delivery.fromSchemaMimeType,
179
+ fitMode: delivery.fitMode, removeBackground: delivery.removeBackground, paddingColor: delivery.paddingColor,
180
+ removeBackgroundFromSchema: hasSchemaBackground,
181
+ imageModelRef: typeof ext?.imageModelRef === 'string' ? ext.imageModelRef : undefined,
182
+ };
183
+ };
184
+ const contexts = [];
185
+ let unresolvedSharedField = false;
186
+ for (const field of fields.length ? fields : [undefined]) {
187
+ try {
188
+ contexts.push(await contextForField(field));
189
+ }
190
+ catch (error) {
191
+ const selected = field?.path.length === targetFieldPath?.length
192
+ && field?.path.every((key, index) => key === targetFieldPath?.[index]);
193
+ if (!targetFieldPath || selected)
194
+ throw error;
195
+ // Invalid sibling requirements still protect the original shared file.
196
+ unresolvedSharedField = true;
197
+ }
198
+ }
199
+ const context = targetFieldPath ? contexts.find(candidate => candidate.fieldPath?.length === targetFieldPath.length
200
+ && candidate.fieldPath.every((key, index) => key === targetFieldPath[index])) : contexts[0];
201
+ const deliveryKey = ({ fieldPath: _fieldPath, outputFormatFromSchema: _formatSource, removeBackgroundFromSchema: _backgroundSource, aspectRatio, ...delivery }) => {
202
+ const ratio = aspectRatio.match(/^\s*(\d+(?:\.\d+)?)\s*:\s*(\d+(?:\.\d+)?)\s*$/);
203
+ return JSON.stringify({ ...delivery, aspectRatio: ratio && Number(ratio[2]) > 0 ? Number(ratio[1]) / Number(ratio[2]) : aspectRatio });
204
+ };
205
+ const conflicting = unresolvedSharedField || contexts.some(candidate => deliveryKey(candidate) !== deliveryKey(context));
206
+ if (conflicting && !targetFieldPath) {
207
+ throw new Error(`Multiple image fields use ${relativeTarget} with different references or delivery constraints. Set targetFieldPath (or --target-field-path) to one of: ${fields.map(field => JSON.stringify(field.path)).join(', ')}.`);
208
+ }
209
+ if (targetFieldPath) {
210
+ // A separate output retains exactly the selected field's requirements.
211
+ if (conflicting || contexts.some(candidate => (candidate.outputFormatFromSchema && !context.outputFormatFromSchema)
212
+ || (candidate.removeBackgroundFromSchema && !context.removeBackgroundFromSchema)))
213
+ context.requiresSeparateOutput = true;
214
+ }
215
+ else {
216
+ context.outputFormatFromSchema = contexts.some(candidate => candidate.outputFormatFromSchema);
217
+ context.removeBackgroundFromSchema = contexts.some(candidate => candidate.removeBackgroundFromSchema);
218
+ }
219
+ assertImageAssetOutputPaths(context, options.outputPaths ?? [], root, options.allowedOutputRoots);
220
+ if (context.referenceImagePaths.length > 8)
64
221
  throw new Error('At most 8 reference images are allowed.');
65
- for (const ref of referenceImagePaths) {
222
+ for (const ref of context.referenceImagePaths) {
66
223
  if (/^https?:\/\//i.test(ref))
67
224
  throw new Error('Download the schema reference into the project before generating.');
68
225
  await sharp(projectFile(root, ref)).metadata();
69
226
  }
70
- const targetMeta = await sharp(target).metadata();
71
- const hasSchemaBackground = typeof ext?.remove_background === 'boolean';
72
- const targetStats = !hasSchemaBackground && targetMeta.hasAlpha ? await sharp(target).stats() : undefined;
73
- const targetHasTransparency = !!targetStats && targetStats.channels.at(-1).min < 250;
74
- const removeBackground = resolveImageBackgroundRequirement({
75
- prompt: options.prompt ?? '',
76
- schemaRemoveBackground: hasSchemaBackground ? ext.remove_background : undefined,
77
- removeBackground: options.removeBackground,
78
- fallbackRemoveBackground: targetHasTransparency,
79
- });
80
- const delivery = resolveAiImageTargetConstraints({
81
- schemaResolution: ext?.resolution,
82
- schemaMimeType: xp?.mime_type,
83
- schemaFitMode: xp?.fit_mode ?? ext?.fit_mode,
84
- schemaRemoveBackground: removeBackground,
85
- schemaPaddingColor: ext?.padding_color,
86
- assetPath: relativeTarget,
87
- referenceWidth: targetMeta.width,
88
- referenceHeight: targetMeta.height,
89
- referenceFormat: targetMeta.format,
90
- });
91
- if (!['png', 'webp', 'jpeg'].includes(delivery.format))
92
- throw new Error('Unsupported image delivery format; use a dedicated animation or vector workflow.');
93
- return {
94
- targetAssetPath: relativeTarget,
95
- fieldPath: field?.path,
96
- referenceImagePaths,
97
- width: delivery.width, height: delivery.height,
98
- aspectRatio: typeof ext?.ratio === 'string' ? ext.ratio : `${delivery.width}:${delivery.height}`,
99
- outputFormat: (removeBackground && delivery.format === 'jpeg' && !delivery.fromSchemaMimeType
100
- ? 'png' : delivery.format),
101
- outputFormatFromSchema: delivery.fromSchemaMimeType,
102
- fitMode: delivery.fitMode, removeBackground: delivery.removeBackground, paddingColor: delivery.paddingColor,
103
- removeBackgroundFromSchema: hasSchemaBackground,
104
- imageModelRef: typeof ext?.imageModelRef === 'string' ? ext.imageModelRef : undefined,
105
- };
227
+ return context;
106
228
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@playcraft/cli",
3
- "version": "0.1.39-prod",
3
+ "version": "0.1.41-prod",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "engines": {
@@ -45,8 +45,8 @@
45
45
  "@gltf-transform/core": "^4.3.0",
46
46
  "@gltf-transform/extensions": "^4.3.0",
47
47
  "@gltf-transform/functions": "^4.3.0",
48
- "@playcraft/build": "^0.1.38-beta",
49
- "@playcraft/common": "^0.1.38-beta",
48
+ "@playcraft/build": "^0.1.40-beta",
49
+ "@playcraft/common": "^0.1.40-beta",
50
50
  "chokidar": "^4.0.3",
51
51
  "commander": "^13.1.0",
52
52
  "cors": "^2.8.6",