@drawcall/market 0.1.99 → 0.2.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.
Files changed (95) hide show
  1. package/dist/asset-implementation.d.ts +2 -2
  2. package/dist/asset-implementation.d.ts.map +1 -1
  3. package/dist/cli.js +15 -3
  4. package/dist/cli.js.map +1 -1
  5. package/dist/commands/install.js +10 -7
  6. package/dist/commands/install.js.map +1 -1
  7. package/dist/commands/list.d.ts +5 -5
  8. package/dist/commands/list.d.ts.map +1 -1
  9. package/dist/commands/list.js +22 -9
  10. package/dist/commands/list.js.map +1 -1
  11. package/dist/commands/pack.d.ts.map +1 -1
  12. package/dist/commands/pack.js +2 -2
  13. package/dist/commands/pack.js.map +1 -1
  14. package/dist/commands/search.d.ts +2 -0
  15. package/dist/commands/search.d.ts.map +1 -1
  16. package/dist/commands/search.js +14 -2
  17. package/dist/commands/search.js.map +1 -1
  18. package/dist/commands/sync.d.ts +6 -0
  19. package/dist/commands/sync.d.ts.map +1 -0
  20. package/dist/commands/sync.js +22 -0
  21. package/dist/commands/sync.js.map +1 -0
  22. package/dist/commands/upload.d.ts.map +1 -1
  23. package/dist/commands/upload.js +12 -7
  24. package/dist/commands/upload.js.map +1 -1
  25. package/dist/contract.d.ts +4 -1
  26. package/dist/contract.d.ts.map +1 -1
  27. package/dist/index.d.ts +3 -2
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +4 -1
  30. package/dist/index.js.map +1 -1
  31. package/dist/install.d.ts.map +1 -1
  32. package/dist/install.js +69 -27
  33. package/dist/install.js.map +1 -1
  34. package/dist/output.d.ts +3 -4
  35. package/dist/output.d.ts.map +1 -1
  36. package/dist/output.js +40 -7
  37. package/dist/output.js.map +1 -1
  38. package/dist/pack.d.ts +36 -6
  39. package/dist/pack.d.ts.map +1 -1
  40. package/dist/pack.js +230 -57
  41. package/dist/pack.js.map +1 -1
  42. package/dist/package-json.d.ts +4 -3
  43. package/dist/package-json.d.ts.map +1 -1
  44. package/dist/package-json.js +3 -1
  45. package/dist/package-json.js.map +1 -1
  46. package/dist/resolve.d.ts +8 -1
  47. package/dist/resolve.d.ts.map +1 -1
  48. package/dist/resolve.js +35 -3
  49. package/dist/resolve.js.map +1 -1
  50. package/dist/schemas.d.ts +19 -2
  51. package/dist/schemas.d.ts.map +1 -1
  52. package/dist/schemas.js +21 -1
  53. package/dist/schemas.js.map +1 -1
  54. package/dist/skill.d.ts +1 -1
  55. package/dist/skill.d.ts.map +1 -1
  56. package/dist/skill.js +4 -2
  57. package/dist/skill.js.map +1 -1
  58. package/dist/v1/client.d.ts +15 -0
  59. package/dist/v1/client.d.ts.map +1 -0
  60. package/dist/v1/client.js +18 -0
  61. package/dist/v1/client.js.map +1 -0
  62. package/dist/v1/contract.d.ts +206 -0
  63. package/dist/v1/contract.d.ts.map +1 -0
  64. package/dist/v1/contract.js +77 -0
  65. package/dist/v1/contract.js.map +1 -0
  66. package/dist/v1/index.d.ts +3 -0
  67. package/dist/v1/index.d.ts.map +1 -0
  68. package/dist/v1/index.js +3 -0
  69. package/dist/v1/index.js.map +1 -0
  70. package/package.json +2 -1
  71. package/skills/market/SKILL.md +4 -2
  72. package/src/asset-implementation.ts +2 -2
  73. package/src/cli.ts +20 -4
  74. package/src/commands/install.ts +11 -8
  75. package/src/commands/list.ts +28 -14
  76. package/src/commands/pack.ts +7 -2
  77. package/src/commands/search.ts +21 -2
  78. package/src/commands/sync.ts +30 -0
  79. package/src/commands/upload.ts +18 -8
  80. package/src/index.ts +11 -0
  81. package/src/install.ts +89 -35
  82. package/src/output.ts +52 -11
  83. package/src/pack.ts +317 -70
  84. package/src/package-json.ts +7 -4
  85. package/src/resolve.ts +47 -4
  86. package/src/schemas.ts +31 -1
  87. package/src/skill.ts +4 -2
  88. package/src/v1/client.ts +28 -0
  89. package/src/v1/contract.ts +140 -0
  90. package/src/v1/index.ts +2 -0
  91. package/dist/market-lock.d.ts +0 -16
  92. package/dist/market-lock.d.ts.map +0 -1
  93. package/dist/market-lock.js +0 -48
  94. package/dist/market-lock.js.map +0 -1
  95. package/src/market-lock.ts +0 -68
package/src/resolve.ts CHANGED
@@ -12,7 +12,12 @@
12
12
  import * as semver from 'semver'
13
13
  import type { MarketClient } from './client.js'
14
14
  import type { AssetExactResult } from './contract.js'
15
- import { semverSchema } from './schemas.js'
15
+ import {
16
+ assetDependencyAlias,
17
+ assetDependencyRange,
18
+ semverSchema,
19
+ type AssetDependencies,
20
+ } from './schemas.js'
16
21
 
17
22
  export interface ResolvedAsset {
18
23
  name: string
@@ -20,7 +25,7 @@ export interface ResolvedAsset {
20
25
  version: string
21
26
  description: string | null
22
27
  npmDependencies: Record<string, string>
23
- assetDependencies: Record<string, string>
28
+ assetDependencies: AssetDependencies
24
29
  skillDependencies: Record<string, string>
25
30
  }
26
31
 
@@ -28,6 +33,12 @@ export interface ResolveResult {
28
33
  assets: ResolvedAsset[]
29
34
  npmDependencies: Record<string, string>
30
35
  skillDependencies: Record<string, string>
36
+ /**
37
+ * File aliases declared by dependents, keyed by dependency name: canonical install path → the
38
+ * path the dependent's project keeps the file at. Install writes files through this map (a
39
+ * template whose author renamed a dependency's files reinstalls them at the renamed locations).
40
+ */
41
+ assetAliases?: Record<string, Record<string, string>>
31
42
  }
32
43
 
33
44
  interface AssetRequest {
@@ -96,6 +107,7 @@ export async function resolve(
96
107
  assets,
97
108
  npmDependencies: mergeNpmDependencies(assets),
98
109
  skillDependencies: mergeSkillDependencies(assets),
110
+ assetAliases: mergeAssetAliases(assets),
99
111
  }
100
112
  }
101
113
 
@@ -141,7 +153,7 @@ async function resolvePass(
141
153
  const selectedVersion = exactVersion
142
154
  ? exactMetadataVersion(meta, assetName, exactVersion)
143
155
  : latestMetadataVersion(meta)
144
- const assetDependencies = parseDependencies(meta.assetDependencies, assetName, 'asset')
156
+ const assetDependencies = parseAssetDependencies(meta.assetDependencies, assetName)
145
157
  resolved.set(assetName, {
146
158
  name: assetName,
147
159
  type: meta.type,
@@ -152,7 +164,8 @@ async function resolvePass(
152
164
  skillDependencies: parseDependencies(meta.skillDependencies, assetName, 'skill'),
153
165
  })
154
166
 
155
- for (const [dependencyName, dependencyRange] of Object.entries(assetDependencies)) {
167
+ for (const [dependencyName, dependencyValue] of Object.entries(assetDependencies)) {
168
+ const dependencyRange = assetDependencyRange(dependencyValue)
156
169
  addConstraint(constraints, dependencyName, dependencyRange, `${assetName}@${selectedVersion}`)
157
170
  pending.push(dependencyName)
158
171
  }
@@ -203,6 +216,36 @@ function mergeSkillDependencies(assets: ResolvedAsset[]): Record<string, string>
203
216
  return merged
204
217
  }
205
218
 
219
+ /**
220
+ * Merge file aliases from every resolved asset, keyed by the dependency they apply to. Two
221
+ * dependents rarely alias the same file; when they do, the first resolved asset wins — a
222
+ * deterministic choice that keeps installs reproducible.
223
+ */
224
+ function mergeAssetAliases(assets: ResolvedAsset[]): Record<string, Record<string, string>> {
225
+ const merged: Record<string, Record<string, string>> = {}
226
+ for (const asset of assets) {
227
+ for (const [dependencyName, value] of Object.entries(asset.assetDependencies)) {
228
+ const alias = assetDependencyAlias(value)
229
+ if (Object.keys(alias).length === 0) continue
230
+ merged[dependencyName] = { ...alias, ...(merged[dependencyName] ?? {}) }
231
+ }
232
+ }
233
+ return merged
234
+ }
235
+
236
+ /** Like parseDependencies, but asset dependency values may also be `{ version, alias }` objects. */
237
+ function parseAssetDependencies(value: string, assetName: string): AssetDependencies {
238
+ try {
239
+ const parsed = JSON.parse(value || '{}') as unknown
240
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
241
+ return parsed as AssetDependencies
242
+ }
243
+ } catch {
244
+ // fall through to a clear error below
245
+ }
246
+ throw new ResolutionError(`Asset "${assetName}" has malformed asset dependency metadata.`)
247
+ }
248
+
206
249
  function parseDependencies(
207
250
  value: string,
208
251
  assetName: string,
package/src/schemas.ts CHANGED
@@ -39,7 +39,37 @@ export const assetNameSchema = z
39
39
 
40
40
  export const npmDependenciesSchema = z.record(z.string(), z.string()).default({})
41
41
 
42
- export const assetDependenciesSchema = z.record(z.string(), z.string()).default({})
42
+ // An asset dependency is either a plain range ("^1.0.0") or an object carrying the range plus
43
+ // `alias`: a map of the dependency's canonical install paths to where this project keeps the file
44
+ // after renaming/moving it. Install writes each file at `alias[path] ?? path`; pack records the
45
+ // aliases it detects by content hash, so renamed installed files are omitted from packs and
46
+ // restored to their renamed locations on reinstall.
47
+ export const assetDependencyAliasSchema = z.record(z.string(), z.string())
48
+
49
+ export const assetDependencyValueSchema = z.union([
50
+ z.string(),
51
+ z.object({ version: z.string(), alias: assetDependencyAliasSchema.optional() }),
52
+ ])
53
+ export type AssetDependencyValue = z.infer<typeof assetDependencyValueSchema>
54
+ export type AssetDependencies = Record<string, AssetDependencyValue>
55
+
56
+ export const assetDependenciesSchema = z.record(z.string(), assetDependencyValueSchema).default({})
57
+
58
+ export function assetDependencyRange(value: AssetDependencyValue): string {
59
+ return typeof value === 'string' ? value : value.version
60
+ }
61
+
62
+ export function assetDependencyAlias(value: AssetDependencyValue): Record<string, string> {
63
+ return typeof value === 'string' ? {} : (value.alias ?? {})
64
+ }
65
+
66
+ /** The slimmest value expressing `range` + `alias`: a plain string when there are no aliases. */
67
+ export function assetDependencyValue(
68
+ range: string,
69
+ alias: Record<string, string>,
70
+ ): AssetDependencyValue {
71
+ return Object.keys(alias).length === 0 ? range : { version: range, alias }
72
+ }
43
73
 
44
74
  // Maps a skill label to a `skills add` source: a remote ref (owner/repo,
45
75
  // git/HTTP URL, tree/<branch>/<subpath>) or a local path to a skill directory
package/src/skill.ts CHANGED
@@ -31,7 +31,9 @@ Install exact refs together in one call; names need no \`--type\`:
31
31
 
32
32
  Install prints the exact files it wrote and records the dependency so packing and later rebuilds can restore it. It never searches or generates. With no refs it installs \`assetDependencies\` from the nearest \`package.json\`. Assets may install declared skill dependencies with \`npx skills add\`. Use \`--force\` only with permission to overwrite changed local files.
33
33
 
34
- \`list --cwd "$PWD"\` is an offline, compact inventory from \`.drawcall/market-lock.json\`. Add \`--files\` when exact paths for previously installed assets are needed; fresh install and generation already print their paths. When selected refs are already installed, read this inventory instead of reinstalling them.
34
+ \`list --cwd "$PWD"\` is an offline, compact inventory of the \`assetDependencies\` in the nearest \`package.json\`, including file aliases. When selected refs are already installed, read this inventory instead of reinstalling them.
35
+
36
+ An \`assetDependencies\` entry is either a range string or \`{ "version": "<range>", "alias": { "<canonical path>": "<current path>" } }\`. An alias records where the project keeps a renamed installed file: install writes the file at the aliased path, and packing omits exactly that one copy (extra duplicates ship as project content). Run \`sync --cwd "$PWD"\` after renaming installed files to record aliases in \`package.json\`; template packing runs the same update automatically.
35
37
 
36
38
  ## Generate
37
39
 
@@ -47,7 +49,7 @@ Add \`--access public\` or \`--access private\` when visibility matters. Omitted
47
49
 
48
50
  ## Publish only when asked
49
51
 
50
- Use \`pack <source> --out <zip>\` to create a Market zip and \`upload <name> <source> "<description>" --type <type>\` to publish it. Both accept repeatable \`--npm name@range\`, \`--asset name@range\`, and \`--skill label=source\` dependencies. Template packing reads root \`assetDependencies\` and may use the API to omit unchanged installed dependency files. A skill source is \`owner/repo\`, a git URL, a full GitHub \`tree/<branch>/<subpath>\` URL, or a local skill directory inside the zip. Use the command's \`--help\` for syntax.
52
+ Use \`pack <source> --out <zip>\` to create a Market zip and \`upload <name> <source> "<description>" --type <type>\` to publish it. Both accept repeatable \`--npm name@range\`, \`--asset name@range\`, and \`--skill label=source\` dependencies. Template packing reads root \`assetDependencies\` and may use the API to omit installed dependency files matched by content hash — even when renamed — recording renames as aliases. A skill source is \`owner/repo\`, a git URL, a full GitHub \`tree/<branch>/<subpath>\` URL, or a local skill directory inside the zip. Use the command's \`--help\` for syntax.
51
53
 
52
54
  Use \`--unapproved\` only when explicitly requested. Do not install an unapproved asset without acceptance. If login is required, ask before running \`npx @drawcall/market login\`.
53
55
  `
@@ -0,0 +1,28 @@
1
+ import { createORPCClient } from '@orpc/client'
2
+ import { OpenAPILink } from '@orpc/openapi-client/fetch'
3
+ import type { ContractRouterClient } from '@orpc/contract'
4
+ import { contract, type V1Contract } from './contract.js'
5
+
6
+ export type MarketV1Client = ContractRouterClient<V1Contract>
7
+
8
+ const DEFAULT_BASE_URL = 'https://market.drawcall.ai'
9
+
10
+ export interface MarketV1ClientOptions {
11
+ baseUrl?: string
12
+ fetch?: typeof globalThis.fetch
13
+ authToken?: string
14
+ }
15
+
16
+ /**
17
+ * A typed client for the v1 REST surface (`/api/v1`). Same call shapes as the legacy RPC client —
18
+ * `client.asset.exact({ name })` — but speaking plain HTTP: reads are GETs on resource URLs, so
19
+ * they are curl-able, shareable and cacheable.
20
+ */
21
+ export function createClient(opts: MarketV1ClientOptions = {}): MarketV1Client {
22
+ const link = new OpenAPILink(contract, {
23
+ url: new URL('/api/v1', opts.baseUrl ?? DEFAULT_BASE_URL).href,
24
+ fetch: opts.fetch,
25
+ headers: opts.authToken ? { authorization: `Bearer ${opts.authToken}` } : undefined,
26
+ })
27
+ return createORPCClient<MarketV1Client>(link)
28
+ }
@@ -0,0 +1,140 @@
1
+ import { oc } from '@orpc/contract'
2
+ import { z } from 'zod'
3
+ import type { AssetFileManifest } from '../schemas.js'
4
+ import type {
5
+ AssetExactResult,
6
+ AssetFileEntry,
7
+ AssetInstallMetadata,
8
+ AssetSearchResult,
9
+ AssetVersion,
10
+ PaginatedList,
11
+ User,
12
+ } from '../contract.js'
13
+ import {
14
+ MAX_UPLOAD_ZIP_SIZE_BYTES,
15
+ agentRunStatusSchema,
16
+ agentStartSchema,
17
+ agentStatusSchema,
18
+ assetFilesSchema,
19
+ exactAssetSchema,
20
+ fileManifestSchema,
21
+ generateAssetSchema,
22
+ generateJobStatusSchema,
23
+ generateResponseSchema,
24
+ generateStatusInputSchema,
25
+ listAssetsSchema,
26
+ updateProfileSchema,
27
+ uploadZipSchema,
28
+ } from '../schemas.js'
29
+
30
+ /**
31
+ * The v1 manifest of an asset version — the one linkable, cacheable answer to "what is this
32
+ * version": its metadata plus every stored file with a fetch-ready content-plane URL, the preview
33
+ * URL, and the zip URL. All URLs are minted by the server and MUST be treated as opaque by
34
+ * clients; that opacity is what lets the content plane evolve without client releases.
35
+ */
36
+ export interface AssetVersionManifest {
37
+ name: string
38
+ type: string
39
+ version: string
40
+ description: string | null
41
+ approved: boolean
42
+ access: 'public' | 'private'
43
+ npmDependencies: string
44
+ assetDependencies: string
45
+ skillDependencies: string
46
+ files: AssetFileEntry[]
47
+ previewUrl: string | null
48
+ zipUrl: string
49
+ }
50
+
51
+ /**
52
+ * The v1 contract: the same procedure tree as the legacy RPC surface (so call sites migrate by
53
+ * swapping only the client), with HTTP routes attached — served as plain REST under `/api/v1`.
54
+ * The route annotations here ARE the control-plane URL contract.
55
+ */
56
+ export const contract = {
57
+ asset: {
58
+ search: oc
59
+ .route({ method: 'GET', path: '/assets' })
60
+ .input(listAssetsSchema)
61
+ .output(z.custom<PaginatedList<AssetSearchResult>>()),
62
+
63
+ exact: oc
64
+ .route({ method: 'GET', path: '/assets/{name}' })
65
+ .input(exactAssetSchema)
66
+ .output(z.custom<AssetExactResult | null>()),
67
+
68
+ // The manifest is new in v1 — it merges the legacy `files` + metadata into one GET.
69
+ manifest: oc
70
+ .route({ method: 'GET', path: '/assets/{name}/{version}' })
71
+ .input(assetFilesSchema)
72
+ .output(z.custom<AssetVersionManifest>()),
73
+
74
+ files: oc
75
+ .route({ method: 'GET', path: '/assets/{name}/{version}/files' })
76
+ .input(assetFilesSchema)
77
+ .output(z.custom<{ files: AssetFileEntry[] }>()),
78
+
79
+ fileManifest: oc
80
+ .route({ method: 'GET', path: '/assets/{name}/{version}/file-manifest' })
81
+ .input(fileManifestSchema)
82
+ .output(z.custom<AssetFileManifest>()),
83
+
84
+ uploadZip: oc
85
+ .route({ method: 'PUT', path: '/assets/{name}/{version}' })
86
+ .input(
87
+ uploadZipSchema
88
+ .extend({ zip: z.instanceof(File) })
89
+ .refine((input) => input.zip.size < MAX_UPLOAD_ZIP_SIZE_BYTES, {
90
+ message: 'Upload zip must be smaller than 1 GB',
91
+ path: ['zip'],
92
+ }),
93
+ )
94
+ .output(z.custom<AssetVersion>()),
95
+
96
+ installMetadata: oc
97
+ .route({ method: 'GET', path: '/types' })
98
+ .output(z.custom<Record<string, AssetInstallMetadata>>()),
99
+
100
+ generate: oc
101
+ .route({ method: 'POST', path: '/generations' })
102
+ .input(generateAssetSchema)
103
+ .output(generateResponseSchema),
104
+
105
+ generateStatus: oc
106
+ .route({ method: 'GET', path: '/generations/{jobId}' })
107
+ .input(generateStatusInputSchema)
108
+ .output(generateJobStatusSchema),
109
+
110
+ agent: {
111
+ start: oc
112
+ .route({ method: 'POST', path: '/agent-runs' })
113
+ .input(agentStartSchema)
114
+ .output(z.object({ runId: z.string() })),
115
+ status: oc
116
+ .route({ method: 'GET', path: '/agent-runs/{runId}' })
117
+ .input(agentStatusSchema)
118
+ .output(agentRunStatusSchema),
119
+ },
120
+ },
121
+
122
+ user: {
123
+ getProfile: oc.route({ method: 'GET', path: '/me' }).output(z.custom<User | null>()),
124
+
125
+ updateProfile: oc
126
+ .route({ method: 'PATCH', path: '/me' })
127
+ .input(updateProfileSchema)
128
+ .output(z.custom<User>()),
129
+
130
+ getAuthToken: oc
131
+ .route({ method: 'GET', path: '/me/auth-token' })
132
+ .output(z.custom<{ prefix: string; createdAt: Date } | null>()),
133
+
134
+ createAuthToken: oc
135
+ .route({ method: 'POST', path: '/me/auth-token' })
136
+ .output(z.object({ token: z.string(), prefix: z.string() })),
137
+ },
138
+ }
139
+
140
+ export type V1Contract = typeof contract
@@ -0,0 +1,2 @@
1
+ export { contract, type V1Contract, type AssetVersionManifest } from './contract.js'
2
+ export { createClient, type MarketV1Client, type MarketV1ClientOptions } from './client.js'
@@ -1,16 +0,0 @@
1
- export declare const MARKET_LOCK_PATH = ".drawcall/market-lock.json";
2
- export interface MarketLockAsset {
3
- version: string;
4
- type: string;
5
- /** Install paths of the asset's files. Content hashes now live on the server (`asset.fileManifest`). */
6
- files: string[];
7
- }
8
- export interface MarketLock {
9
- version: 1;
10
- assets: Record<string, MarketLockAsset>;
11
- }
12
- export declare function readMarketLock(projectRoot: string): Promise<MarketLock>;
13
- export declare function writeMarketLock(projectRoot: string, lock: MarketLock): Promise<void>;
14
- export declare function emptyMarketLock(): MarketLock;
15
- export declare function sha256(bytes: Uint8Array): string;
16
- //# sourceMappingURL=market-lock.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"market-lock.d.ts","sourceRoot":"","sources":["../src/market-lock.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,gBAAgB,+BAA+B,CAAA;AAE5D,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,MAAM,CAAA;IACZ,wGAAwG;IACxG,KAAK,EAAE,MAAM,EAAE,CAAA;CAChB;AAED,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,CAAC,CAAA;IACV,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAA;CACxC;AAqBD,wBAAsB,cAAc,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAQ7E;AAED,wBAAsB,eAAe,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAI1F;AAED,wBAAgB,eAAe,IAAI,UAAU,CAK5C;AAED,wBAAgB,MAAM,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAEhD"}
@@ -1,48 +0,0 @@
1
- import { createHash } from 'crypto';
2
- import * as fs from 'fs/promises';
3
- import * as path from 'path';
4
- import { z } from 'zod';
5
- export const MARKET_LOCK_PATH = '.drawcall/market-lock.json';
6
- // Content hashes moved to the server, so the lock records only install paths. It still reads the
7
- // legacy `{ path: { sha256 } }` shape and normalizes it to a path list, so an existing lock keeps
8
- // working and is rewritten to the slim form on the next install.
9
- const filesSchema = z
10
- .union([z.array(z.string()), z.record(z.string(), z.object({ sha256: z.string() }))])
11
- .transform((files) => (Array.isArray(files) ? files : Object.keys(files)));
12
- const marketLockSchema = z.object({
13
- version: z.literal(1),
14
- assets: z.record(z.string(), z.object({
15
- version: z.string(),
16
- type: z.string(),
17
- files: filesSchema,
18
- })),
19
- });
20
- export async function readMarketLock(projectRoot) {
21
- const lockPath = path.join(projectRoot, MARKET_LOCK_PATH);
22
- try {
23
- return marketLockSchema.parse(JSON.parse(await fs.readFile(lockPath, 'utf-8')));
24
- }
25
- catch (error) {
26
- if (isMissingFile(error))
27
- return emptyMarketLock();
28
- throw error;
29
- }
30
- }
31
- export async function writeMarketLock(projectRoot, lock) {
32
- const lockPath = path.join(projectRoot, MARKET_LOCK_PATH);
33
- await fs.mkdir(path.dirname(lockPath), { recursive: true });
34
- await fs.writeFile(lockPath, JSON.stringify(lock, null, 2) + '\n');
35
- }
36
- export function emptyMarketLock() {
37
- return {
38
- version: 1,
39
- assets: {},
40
- };
41
- }
42
- export function sha256(bytes) {
43
- return createHash('sha256').update(bytes).digest('hex');
44
- }
45
- function isMissingFile(error) {
46
- return error instanceof Error && 'code' in error && error.code === 'ENOENT';
47
- }
48
- //# sourceMappingURL=market-lock.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"market-lock.js","sourceRoot":"","sources":["../src/market-lock.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAA;AACnC,OAAO,KAAK,EAAE,MAAM,aAAa,CAAA;AACjC,OAAO,KAAK,IAAI,MAAM,MAAM,CAAA;AAC5B,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB,MAAM,CAAC,MAAM,gBAAgB,GAAG,4BAA4B,CAAA;AAc5D,iGAAiG;AACjG,kGAAkG;AAClG,iEAAiE;AACjE,MAAM,WAAW,GAAG,CAAC;KAClB,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC;KACpF,SAAS,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;AAE5E,MAAM,gBAAgB,GAAG,CAAC,CAAC,MAAM,CAAC;IAChC,OAAO,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;IACrB,MAAM,EAAE,CAAC,CAAC,MAAM,CACd,CAAC,CAAC,MAAM,EAAE,EACV,CAAC,CAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE;QACnB,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;QAChB,KAAK,EAAE,WAAW;KACnB,CAAC,CACH;CACF,CAAC,CAAA;AAEF,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,WAAmB;IACtD,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,gBAAgB,CAAC,CAAA;IACzD,IAAI,CAAC;QACH,OAAO,gBAAgB,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAC,CAAA;IACjF,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,aAAa,CAAC,KAAK,CAAC;YAAE,OAAO,eAAe,EAAE,CAAA;QAClD,MAAM,KAAK,CAAA;IACb,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,WAAmB,EAAE,IAAgB;IACzE,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,gBAAgB,CAAC,CAAA;IACzD,MAAM,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;IAC3D,MAAM,EAAE,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,CAAA;AACpE,CAAC;AAED,MAAM,UAAU,eAAe;IAC7B,OAAO;QACL,OAAO,EAAE,CAAC;QACV,MAAM,EAAE,EAAE;KACX,CAAA;AACH,CAAC;AAED,MAAM,UAAU,MAAM,CAAC,KAAiB;IACtC,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;AACzD,CAAC;AAED,SAAS,aAAa,CAAC,KAAc;IACnC,OAAO,KAAK,YAAY,KAAK,IAAI,MAAM,IAAI,KAAK,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ,CAAA;AAC7E,CAAC"}
@@ -1,68 +0,0 @@
1
- import { createHash } from 'crypto'
2
- import * as fs from 'fs/promises'
3
- import * as path from 'path'
4
- import { z } from 'zod'
5
-
6
- export const MARKET_LOCK_PATH = '.drawcall/market-lock.json'
7
-
8
- export interface MarketLockAsset {
9
- version: string
10
- type: string
11
- /** Install paths of the asset's files. Content hashes now live on the server (`asset.fileManifest`). */
12
- files: string[]
13
- }
14
-
15
- export interface MarketLock {
16
- version: 1
17
- assets: Record<string, MarketLockAsset>
18
- }
19
-
20
- // Content hashes moved to the server, so the lock records only install paths. It still reads the
21
- // legacy `{ path: { sha256 } }` shape and normalizes it to a path list, so an existing lock keeps
22
- // working and is rewritten to the slim form on the next install.
23
- const filesSchema = z
24
- .union([z.array(z.string()), z.record(z.string(), z.object({ sha256: z.string() }))])
25
- .transform((files) => (Array.isArray(files) ? files : Object.keys(files)))
26
-
27
- const marketLockSchema = z.object({
28
- version: z.literal(1),
29
- assets: z.record(
30
- z.string(),
31
- z.object({
32
- version: z.string(),
33
- type: z.string(),
34
- files: filesSchema,
35
- }),
36
- ),
37
- })
38
-
39
- export async function readMarketLock(projectRoot: string): Promise<MarketLock> {
40
- const lockPath = path.join(projectRoot, MARKET_LOCK_PATH)
41
- try {
42
- return marketLockSchema.parse(JSON.parse(await fs.readFile(lockPath, 'utf-8')))
43
- } catch (error) {
44
- if (isMissingFile(error)) return emptyMarketLock()
45
- throw error
46
- }
47
- }
48
-
49
- export async function writeMarketLock(projectRoot: string, lock: MarketLock): Promise<void> {
50
- const lockPath = path.join(projectRoot, MARKET_LOCK_PATH)
51
- await fs.mkdir(path.dirname(lockPath), { recursive: true })
52
- await fs.writeFile(lockPath, JSON.stringify(lock, null, 2) + '\n')
53
- }
54
-
55
- export function emptyMarketLock(): MarketLock {
56
- return {
57
- version: 1,
58
- assets: {},
59
- }
60
- }
61
-
62
- export function sha256(bytes: Uint8Array): string {
63
- return createHash('sha256').update(bytes).digest('hex')
64
- }
65
-
66
- function isMissingFile(error: unknown): boolean {
67
- return error instanceof Error && 'code' in error && error.code === 'ENOENT'
68
- }