@drawcall/market 0.1.25 → 0.1.27
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/dist/cli-client.d.ts.map +1 -1
- package/dist/cli-client.js +1 -1
- package/dist/cli-client.js.map +1 -1
- package/dist/cli.js +15 -15
- package/dist/cli.js.map +1 -1
- package/dist/client.d.ts +0 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +1 -2
- package/dist/client.js.map +1 -1
- package/dist/commands/install.d.ts +8 -4
- package/dist/commands/install.d.ts.map +1 -1
- package/dist/commands/install.js +24 -59
- package/dist/commands/install.js.map +1 -1
- package/dist/commands/preview.d.ts +0 -2
- package/dist/commands/preview.d.ts.map +1 -1
- package/dist/commands/preview.js +4 -6
- package/dist/commands/preview.js.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +9 -3
- package/dist/config.js.map +1 -1
- package/dist/install.js +6 -8
- package/dist/install.js.map +1 -1
- package/dist/resolve.d.ts +4 -3
- package/dist/resolve.d.ts.map +1 -1
- package/dist/resolve.js +38 -19
- package/dist/resolve.js.map +1 -1
- package/dist/skill.d.ts +1 -1
- package/dist/skill.d.ts.map +1 -1
- package/dist/skill.js +16 -12
- package/dist/skill.js.map +1 -1
- package/package.json +1 -1
- package/src/cli-client.ts +1 -2
- package/src/cli.ts +26 -32
- package/src/client.ts +1 -3
- package/src/commands/install.ts +30 -77
- package/src/commands/preview.ts +5 -10
- package/src/config.ts +9 -3
- package/src/install.ts +6 -7
- package/src/resolve.ts +46 -21
- package/src/skill.ts +16 -12
- package/tests/install-command.test.ts +13 -46
package/src/resolve.ts
CHANGED
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
* Dependency resolver for market assets.
|
|
3
3
|
*
|
|
4
4
|
* 1. Collects the target assets and all transitive asset dependencies.
|
|
5
|
-
* 2. For each asset,
|
|
6
|
-
* every dependent.
|
|
7
|
-
*
|
|
5
|
+
* 2. For each asset, takes the latest published version and verifies it
|
|
6
|
+
* satisfies ALL constraints from every dependent. (Only the latest
|
|
7
|
+
* version is resolved; older versions are not considered.)
|
|
8
|
+
* 3. Intersects npm dependency ranges across all resolved assets and checks
|
|
8
9
|
* that they are compatible.
|
|
9
10
|
*/
|
|
10
11
|
|
|
@@ -58,24 +59,26 @@ export async function resolve(
|
|
|
58
59
|
}
|
|
59
60
|
|
|
60
61
|
if (!opts.includeUnapproved && !meta.approved) {
|
|
61
|
-
throw new ResolutionError(
|
|
62
|
-
`Asset "${assetName}" has no ${opts.includeUnapproved ? '' : 'approved '}versions.`,
|
|
63
|
-
)
|
|
62
|
+
throw new ResolutionError(`Asset "${assetName}" has no approved versions.`)
|
|
64
63
|
}
|
|
65
64
|
|
|
65
|
+
// The market publishes immutable versions and resolution targets the
|
|
66
|
+
// latest published version of each asset. A range that excludes the
|
|
67
|
+
// latest version is therefore unsatisfiable (older versions are not
|
|
68
|
+
// resolved), which this message makes explicit.
|
|
66
69
|
const assetConstraints = constraints.get(assetName) ?? []
|
|
67
70
|
if (!assetConstraints.every((c) => semver.satisfies(meta.latestVersion, c.range))) {
|
|
68
71
|
const constraintDesc = assetConstraints
|
|
69
72
|
.map((c) => ` ${c.range} (from ${c.from})`)
|
|
70
73
|
.join('\n')
|
|
71
74
|
throw new ResolutionError(
|
|
72
|
-
`
|
|
73
|
-
`
|
|
75
|
+
`The latest${opts.includeUnapproved ? '' : ' approved'} version of "${assetName}" ` +
|
|
76
|
+
`(${meta.latestVersion}) does not satisfy all constraints:\n${constraintDesc}`,
|
|
74
77
|
)
|
|
75
78
|
}
|
|
76
79
|
|
|
77
|
-
const npmDeps
|
|
78
|
-
const assetDeps
|
|
80
|
+
const npmDeps = parseDependencies(meta.npmDependencies, assetName, 'npm')
|
|
81
|
+
const assetDeps = parseDependencies(meta.assetDependencies, assetName, 'asset')
|
|
79
82
|
|
|
80
83
|
resolved.set(assetName, {
|
|
81
84
|
name: assetName,
|
|
@@ -115,6 +118,22 @@ export async function resolve(
|
|
|
115
118
|
}
|
|
116
119
|
}
|
|
117
120
|
|
|
121
|
+
function parseDependencies(
|
|
122
|
+
value: string,
|
|
123
|
+
assetName: string,
|
|
124
|
+
kind: 'npm' | 'asset',
|
|
125
|
+
): Record<string, string> {
|
|
126
|
+
try {
|
|
127
|
+
const parsed = JSON.parse(value || '{}') as unknown
|
|
128
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
129
|
+
return parsed as Record<string, string>
|
|
130
|
+
}
|
|
131
|
+
} catch {
|
|
132
|
+
// fall through to a clear error below
|
|
133
|
+
}
|
|
134
|
+
throw new ResolutionError(`Asset "${assetName}" has malformed ${kind} dependency metadata.`)
|
|
135
|
+
}
|
|
136
|
+
|
|
118
137
|
function addConstraint(
|
|
119
138
|
constraints: Map<string, { range: string; from: string }[]>,
|
|
120
139
|
name: string,
|
|
@@ -151,7 +170,8 @@ async function fetchMeta(
|
|
|
151
170
|
/**
|
|
152
171
|
* Merge npm dependency ranges from all resolved assets.
|
|
153
172
|
* For each package, check that all declared ranges are compatible
|
|
154
|
-
* (using semver.intersects)
|
|
173
|
+
* (using semver.intersects), then return the single declared range that is a
|
|
174
|
+
* subset of every other — the true intersection.
|
|
155
175
|
*/
|
|
156
176
|
function mergeNpmDependencies(assets: ResolvedAsset[]): Record<string, string> {
|
|
157
177
|
// Collect all ranges per package
|
|
@@ -181,18 +201,23 @@ function mergeNpmDependencies(assets: ResolvedAsset[]): Record<string, string> {
|
|
|
181
201
|
}
|
|
182
202
|
}
|
|
183
203
|
|
|
184
|
-
// Use the
|
|
185
|
-
//
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
204
|
+
// Use the single declared range that is contained in every other declared
|
|
205
|
+
// range — the true intersection of all constraints. Picking by "highest
|
|
206
|
+
// minimum version" is unsound: a range with a higher minimum can also have
|
|
207
|
+
// a higher (or open) upper bound, silently dropping another dependent's
|
|
208
|
+
// upper bound. If no single range represents the intersection, it cannot be
|
|
209
|
+
// expressed as one package.json range, so fail loudly.
|
|
210
|
+
const narrowest = ranges.find((candidate) =>
|
|
211
|
+
ranges.every((other) => semver.subset(candidate.range, other.range)),
|
|
212
|
+
)
|
|
213
|
+
if (!narrowest) {
|
|
214
|
+
throw new ResolutionError(
|
|
215
|
+
`npm dependency ranges for "${pkg}" overlap but cannot be combined into a single range:\n` +
|
|
216
|
+
ranges.map((r) => ` ${r.range} (from ${r.from})`).join('\n'),
|
|
217
|
+
)
|
|
193
218
|
}
|
|
194
219
|
|
|
195
|
-
merged[pkg] = narrowest
|
|
220
|
+
merged[pkg] = narrowest.range
|
|
196
221
|
}
|
|
197
222
|
|
|
198
223
|
return merged
|
package/src/skill.ts
CHANGED
|
@@ -5,36 +5,40 @@ description: Find, preview, install, generate, and publish Drawcall Market asset
|
|
|
5
5
|
|
|
6
6
|
# Drawcall Market
|
|
7
7
|
|
|
8
|
-
Use the \`market\` CLI. Keep commands short and read the summary
|
|
8
|
+
Use the \`market\` CLI. Keep commands short and read the summary lines.
|
|
9
9
|
|
|
10
10
|
## Quick Start
|
|
11
11
|
|
|
12
12
|
\`\`\`sh
|
|
13
13
|
market search "wooden chair" --type model --limit 3
|
|
14
|
-
market install wooden-chair --
|
|
14
|
+
market install wooden-chair --cwd "$PWD"
|
|
15
15
|
market preview wooden-chair --out /tmp/wooden-chair.png
|
|
16
16
|
\`\`\`
|
|
17
17
|
|
|
18
18
|
## Workflow
|
|
19
19
|
|
|
20
|
-
1. Search first unless the user gave an exact asset name.
|
|
21
|
-
2.
|
|
22
|
-
3.
|
|
23
|
-
4.
|
|
20
|
+
1. Search first unless the user already gave an exact asset name. \`search\` requires \`--type\`; use \`model\` unless the user names another supported type: \`humanoid-model\`, \`texture\`, \`humanoid-animation\`, \`template\`, \`sound-effect\`, \`background-music\`, or \`environment\`.
|
|
21
|
+
2. Use \`--limit 1\` for lookup, \`--limit 3\` for choice. Search caps at 5. Add \`--verbose\` only when the one-line descriptions are not enough.
|
|
22
|
+
3. \`install\` takes one or more exact asset names (optionally \`name@range\`); it does not search or generate. Find names with \`search\` first. No \`--type\` is needed — asset names are unique.
|
|
23
|
+
4. \`preview <name>\` saves the preview image; no \`--type\` is needed. Not every type has previews (e.g. \`humanoid-animation\`, \`template\`, \`sound-effect\`, \`background-music\`); the CLI reports when one is unavailable.
|
|
24
24
|
5. Use \`--unapproved\` only when the user asks for unapproved/private/admin assets. Do not install unapproved assets without explicit acceptance.
|
|
25
|
-
6.
|
|
26
|
-
7.
|
|
27
|
-
8.
|
|
28
|
-
9. Installed \`environment\` assets contain \`public/environment/<name>.hdr\` for Three.js IBL lighting and \`public/environment/<name>-background.webp\` for the visible equirectangular background. Use \`market preview\` to fetch the preview image separately.
|
|
25
|
+
6. \`generate --type <type> "<prompt>"\` creates a new asset; it requires login and a type that supports generation. No asset type currently supports generation.
|
|
26
|
+
7. Upload only when publishing is requested: \`market upload <name> <zip> "<description>" --type <type>\`.
|
|
27
|
+
8. Installed \`environment\` assets contain \`public/environment/<name>.hdr\` for Three.js IBL lighting and \`public/environment/<name>-background.webp\` for the visible equirectangular background. Use \`market preview\` to fetch the preview image separately.
|
|
29
28
|
|
|
30
29
|
## Output
|
|
31
30
|
|
|
32
|
-
Commands print concise summaries:
|
|
31
|
+
Commands print concise, line-oriented summaries:
|
|
33
32
|
|
|
34
33
|
\`\`\`text
|
|
35
34
|
Results: 2/8 query="wooden chair" type=model approval=approved
|
|
36
35
|
- wooden-chair@1.0.0 | model | approved | Low-poly wooden chair
|
|
37
|
-
Installed
|
|
36
|
+
Installed:
|
|
37
|
+
- wooden-chair@1.0.0 (asset)
|
|
38
|
+
files:
|
|
39
|
+
public/model
|
|
40
|
+
└─ wooden-chair.glb
|
|
41
|
+
- three@^0.178.0 (npm)
|
|
38
42
|
Saved preview for wooden-chair@1.0.0: /tmp/wooden-chair.png
|
|
39
43
|
\`\`\`
|
|
40
44
|
|
|
@@ -2,17 +2,10 @@ import assert from 'node:assert/strict'
|
|
|
2
2
|
import test from 'node:test'
|
|
3
3
|
import { resolveArg } from '../src/commands/install.js'
|
|
4
4
|
|
|
5
|
-
test('resolveArg reports unapproved explicit assets
|
|
6
|
-
let searchCalls = 0
|
|
5
|
+
test('resolveArg reports unapproved explicit assets clearly', async () => {
|
|
7
6
|
const client = {
|
|
8
7
|
asset: {
|
|
9
|
-
exact: async ({
|
|
10
|
-
includeUnapproved,
|
|
11
|
-
}: {
|
|
12
|
-
name: string
|
|
13
|
-
type?: string
|
|
14
|
-
includeUnapproved: boolean
|
|
15
|
-
}) =>
|
|
8
|
+
exact: async ({ includeUnapproved }: { name: string; includeUnapproved: boolean }) =>
|
|
16
9
|
includeUnapproved
|
|
17
10
|
? {
|
|
18
11
|
name: 'qwantani-moon-noon-puresky',
|
|
@@ -20,59 +13,33 @@ test('resolveArg reports unapproved explicit assets instead of falling through t
|
|
|
20
13
|
approved: false,
|
|
21
14
|
}
|
|
22
15
|
: null,
|
|
23
|
-
search: async () => {
|
|
24
|
-
searchCalls += 1
|
|
25
|
-
return { items: [], total: 0, page: 1, limit: 1, totalPages: 0 }
|
|
26
|
-
},
|
|
27
16
|
},
|
|
28
17
|
} as never
|
|
29
18
|
|
|
30
19
|
await assert.rejects(
|
|
31
|
-
resolveArg(
|
|
32
|
-
client,
|
|
33
|
-
'qwantani-moon-noon-puresky@1.0.2',
|
|
34
|
-
'environment',
|
|
35
|
-
silentSpinner(),
|
|
36
|
-
true,
|
|
37
|
-
false,
|
|
38
|
-
),
|
|
20
|
+
resolveArg(client, 'qwantani-moon-noon-puresky@1.0.2', false),
|
|
39
21
|
/has no approved versions/u,
|
|
40
22
|
)
|
|
41
|
-
|
|
42
|
-
assert.equal(searchCalls, 0)
|
|
43
23
|
})
|
|
44
24
|
|
|
45
|
-
test('resolveArg keeps explicit versioned
|
|
46
|
-
let searchCalls = 0
|
|
25
|
+
test('resolveArg keeps explicit versioned refs exact when the asset exists', async () => {
|
|
47
26
|
const client = {
|
|
48
27
|
asset: {
|
|
49
|
-
exact: async () =>
|
|
50
|
-
search: async () => {
|
|
51
|
-
searchCalls += 1
|
|
52
|
-
return { items: [], total: 0, page: 1, limit: 1, totalPages: 0 }
|
|
53
|
-
},
|
|
28
|
+
exact: async () => ({ name: 'fresh-sky', latestVersion: '1.2.3', approved: true }),
|
|
54
29
|
},
|
|
55
30
|
} as never
|
|
56
31
|
|
|
57
|
-
const request = await resolveArg(
|
|
58
|
-
client,
|
|
59
|
-
'fresh-sky@1.2.3',
|
|
60
|
-
'environment',
|
|
61
|
-
silentSpinner(),
|
|
62
|
-
true,
|
|
63
|
-
false,
|
|
64
|
-
)
|
|
32
|
+
const request = await resolveArg(client, 'fresh-sky@1.2.3', false)
|
|
65
33
|
|
|
66
34
|
assert.deepEqual(request, { name: 'fresh-sky', range: '1.2.3' })
|
|
67
|
-
assert.equal(searchCalls, 0)
|
|
68
35
|
})
|
|
69
36
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
start() {
|
|
75
|
-
return this
|
|
37
|
+
test('resolveArg errors when the asset does not exist instead of deferring', async () => {
|
|
38
|
+
const client = {
|
|
39
|
+
asset: {
|
|
40
|
+
exact: async () => null,
|
|
76
41
|
},
|
|
77
42
|
} as never
|
|
78
|
-
|
|
43
|
+
|
|
44
|
+
await assert.rejects(resolveArg(client, 'does-not-exist@1.2.3', false), /not found/u)
|
|
45
|
+
})
|