@drawcall/market 0.1.98 → 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 (104) hide show
  1. package/README.md +7 -4
  2. package/dist/asset-implementation.d.ts +3 -3
  3. package/dist/asset-implementation.d.ts.map +1 -1
  4. package/dist/cli.js +15 -3
  5. package/dist/cli.js.map +1 -1
  6. package/dist/client.d.ts +14 -0
  7. package/dist/client.d.ts.map +1 -1
  8. package/dist/client.js +18 -0
  9. package/dist/client.js.map +1 -1
  10. package/dist/commands/install.js +10 -7
  11. package/dist/commands/install.js.map +1 -1
  12. package/dist/commands/list.d.ts +5 -5
  13. package/dist/commands/list.d.ts.map +1 -1
  14. package/dist/commands/list.js +22 -9
  15. package/dist/commands/list.js.map +1 -1
  16. package/dist/commands/pack.d.ts.map +1 -1
  17. package/dist/commands/pack.js +2 -2
  18. package/dist/commands/pack.js.map +1 -1
  19. package/dist/commands/search.d.ts +2 -0
  20. package/dist/commands/search.d.ts.map +1 -1
  21. package/dist/commands/search.js +14 -2
  22. package/dist/commands/search.js.map +1 -1
  23. package/dist/commands/sync.d.ts +6 -0
  24. package/dist/commands/sync.d.ts.map +1 -0
  25. package/dist/commands/sync.js +22 -0
  26. package/dist/commands/sync.js.map +1 -0
  27. package/dist/commands/upload.d.ts.map +1 -1
  28. package/dist/commands/upload.js +20 -10
  29. package/dist/commands/upload.js.map +1 -1
  30. package/dist/contract.d.ts +4 -6
  31. package/dist/contract.d.ts.map +1 -1
  32. package/dist/contract.js +1 -2
  33. package/dist/contract.js.map +1 -1
  34. package/dist/index.d.ts +5 -4
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.js +5 -2
  37. package/dist/index.js.map +1 -1
  38. package/dist/install.d.ts.map +1 -1
  39. package/dist/install.js +69 -27
  40. package/dist/install.js.map +1 -1
  41. package/dist/output.d.ts +3 -4
  42. package/dist/output.d.ts.map +1 -1
  43. package/dist/output.js +40 -7
  44. package/dist/output.js.map +1 -1
  45. package/dist/pack.d.ts +36 -6
  46. package/dist/pack.d.ts.map +1 -1
  47. package/dist/pack.js +230 -57
  48. package/dist/pack.js.map +1 -1
  49. package/dist/package-json.d.ts +4 -3
  50. package/dist/package-json.d.ts.map +1 -1
  51. package/dist/package-json.js +3 -1
  52. package/dist/package-json.js.map +1 -1
  53. package/dist/resolve.d.ts +8 -1
  54. package/dist/resolve.d.ts.map +1 -1
  55. package/dist/resolve.js +35 -3
  56. package/dist/resolve.js.map +1 -1
  57. package/dist/schemas.d.ts +19 -7
  58. package/dist/schemas.d.ts.map +1 -1
  59. package/dist/schemas.js +22 -7
  60. package/dist/schemas.js.map +1 -1
  61. package/dist/skill.d.ts +1 -1
  62. package/dist/skill.d.ts.map +1 -1
  63. package/dist/skill.js +4 -2
  64. package/dist/skill.js.map +1 -1
  65. package/dist/v1/client.d.ts +15 -0
  66. package/dist/v1/client.d.ts.map +1 -0
  67. package/dist/v1/client.js +18 -0
  68. package/dist/v1/client.js.map +1 -0
  69. package/dist/v1/contract.d.ts +206 -0
  70. package/dist/v1/contract.d.ts.map +1 -0
  71. package/dist/v1/contract.js +77 -0
  72. package/dist/v1/contract.js.map +1 -0
  73. package/dist/v1/index.d.ts +3 -0
  74. package/dist/v1/index.d.ts.map +1 -0
  75. package/dist/v1/index.js +3 -0
  76. package/dist/v1/index.js.map +1 -0
  77. package/package.json +2 -1
  78. package/skills/market/SKILL.md +4 -2
  79. package/src/asset-implementation.ts +3 -3
  80. package/src/cli.ts +20 -4
  81. package/src/client.ts +26 -0
  82. package/src/commands/install.ts +11 -8
  83. package/src/commands/list.ts +28 -14
  84. package/src/commands/pack.ts +7 -2
  85. package/src/commands/search.ts +21 -2
  86. package/src/commands/sync.ts +30 -0
  87. package/src/commands/upload.ts +26 -11
  88. package/src/contract.ts +0 -3
  89. package/src/index.ts +13 -3
  90. package/src/install.ts +89 -35
  91. package/src/output.ts +52 -11
  92. package/src/pack.ts +317 -70
  93. package/src/package-json.ts +7 -4
  94. package/src/resolve.ts +47 -4
  95. package/src/schemas.ts +32 -8
  96. package/src/skill.ts +4 -2
  97. package/src/v1/client.ts +28 -0
  98. package/src/v1/contract.ts +140 -0
  99. package/src/v1/index.ts +2 -0
  100. package/dist/market-lock.d.ts +0 -16
  101. package/dist/market-lock.d.ts.map +0 -1
  102. package/dist/market-lock.js +0 -48
  103. package/dist/market-lock.js.map +0 -1
  104. package/src/market-lock.ts +0 -68
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
@@ -95,14 +125,8 @@ export const uploadZipSchema = z.object({
95
125
  access: assetAccessSchema.optional(),
96
126
  })
97
127
 
98
- export const downloadZipSchema = z.object({
99
- name: assetNameSchema,
100
- version: semverSchema,
101
- key: accessKeySchema.optional(),
102
- })
103
-
104
128
  // Input of `asset.files`: the derived file index of a version, for per-file installs from the data
105
- // plane. Same credential rules as downloadZip.
129
+ // plane. Same credential rules as the `GET /download` zip view: an identified viewer or a `key`.
106
130
  export const assetFilesSchema = z.object({
107
131
  name: assetNameSchema,
108
132
  version: semverSchema,
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
- }