@drawcall/market 0.1.4 → 0.1.6

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 (99) hide show
  1. package/dist/asset-implementation.d.ts +88 -0
  2. package/dist/asset-implementation.d.ts.map +1 -0
  3. package/dist/asset-implementation.js +2 -0
  4. package/dist/asset-implementation.js.map +1 -0
  5. package/dist/cli-client.d.ts +2 -2
  6. package/dist/cli-client.d.ts.map +1 -1
  7. package/dist/cli-client.js +4 -4
  8. package/dist/cli-client.js.map +1 -1
  9. package/dist/cli.js +104 -22
  10. package/dist/cli.js.map +1 -1
  11. package/dist/client.d.ts +1 -3
  12. package/dist/client.d.ts.map +1 -1
  13. package/dist/client.js +4 -12
  14. package/dist/client.js.map +1 -1
  15. package/dist/commands/generate.d.ts.map +1 -1
  16. package/dist/commands/generate.js +9 -4
  17. package/dist/commands/generate.js.map +1 -1
  18. package/dist/commands/install.d.ts.map +1 -1
  19. package/dist/commands/install.js +33 -27
  20. package/dist/commands/install.js.map +1 -1
  21. package/dist/commands/logout.js +3 -3
  22. package/dist/commands/logout.js.map +1 -1
  23. package/dist/commands/preview.d.ts +9 -0
  24. package/dist/commands/preview.d.ts.map +1 -0
  25. package/dist/commands/preview.js +50 -0
  26. package/dist/commands/preview.js.map +1 -0
  27. package/dist/commands/search.d.ts +4 -1
  28. package/dist/commands/search.d.ts.map +1 -1
  29. package/dist/commands/search.js +21 -11
  30. package/dist/commands/search.js.map +1 -1
  31. package/dist/commands/upload.d.ts +9 -0
  32. package/dist/commands/upload.d.ts.map +1 -0
  33. package/dist/commands/upload.js +220 -0
  34. package/dist/commands/upload.js.map +1 -0
  35. package/dist/config.d.ts +4 -7
  36. package/dist/config.d.ts.map +1 -1
  37. package/dist/config.js +8 -4
  38. package/dist/config.js.map +1 -1
  39. package/dist/constants.d.ts.map +1 -1
  40. package/dist/constants.js +2 -10
  41. package/dist/constants.js.map +1 -1
  42. package/dist/contract.d.ts +37 -134
  43. package/dist/contract.d.ts.map +1 -1
  44. package/dist/contract.js +10 -48
  45. package/dist/contract.js.map +1 -1
  46. package/dist/generate.d.ts +0 -2
  47. package/dist/generate.d.ts.map +1 -1
  48. package/dist/generate.js +5 -22
  49. package/dist/generate.js.map +1 -1
  50. package/dist/index.d.ts +5 -6
  51. package/dist/index.d.ts.map +1 -1
  52. package/dist/index.js +2 -3
  53. package/dist/index.js.map +1 -1
  54. package/dist/install.d.ts +1 -1
  55. package/dist/install.d.ts.map +1 -1
  56. package/dist/install.js +14 -11
  57. package/dist/install.js.map +1 -1
  58. package/dist/output.d.ts +26 -0
  59. package/dist/output.d.ts.map +1 -0
  60. package/dist/output.js +52 -0
  61. package/dist/output.js.map +1 -0
  62. package/dist/resolve.d.ts.map +1 -1
  63. package/dist/resolve.js +15 -26
  64. package/dist/resolve.js.map +1 -1
  65. package/dist/schemas.d.ts +31 -39
  66. package/dist/schemas.d.ts.map +1 -1
  67. package/dist/schemas.js +23 -25
  68. package/dist/schemas.js.map +1 -1
  69. package/package.json +13 -4
  70. package/src/asset-implementation.ts +114 -0
  71. package/src/cli-client.ts +7 -6
  72. package/src/cli.ts +144 -29
  73. package/src/client.ts +5 -15
  74. package/src/commands/generate.ts +9 -6
  75. package/src/commands/install.ts +41 -30
  76. package/src/commands/logout.ts +3 -3
  77. package/src/commands/preview.ts +69 -0
  78. package/src/commands/search.ts +28 -12
  79. package/src/commands/upload.ts +262 -0
  80. package/src/config.ts +11 -5
  81. package/src/constants.ts +2 -10
  82. package/src/contract.ts +22 -120
  83. package/src/generate.ts +5 -29
  84. package/src/index.ts +23 -14
  85. package/src/install.ts +24 -14
  86. package/src/output.ts +76 -0
  87. package/src/resolve.ts +22 -38
  88. package/src/schemas.ts +26 -27
  89. package/tsconfig.json +2 -1
  90. package/dist/commands/login.d.ts +0 -10
  91. package/dist/commands/login.d.ts.map +0 -1
  92. package/dist/commands/login.js +0 -92
  93. package/dist/commands/login.js.map +0 -1
  94. package/dist/internal-contract.d.ts +0 -19
  95. package/dist/internal-contract.d.ts.map +0 -1
  96. package/dist/internal-contract.js +0 -19
  97. package/dist/internal-contract.js.map +0 -1
  98. package/src/commands/login.ts +0 -113
  99. package/src/internal-contract.ts +0 -26
package/src/install.ts CHANGED
@@ -6,15 +6,23 @@
6
6
  * 3. Runs the package manager to install npm deps.
7
7
  *
8
8
  * NOTE: This module uses Node.js APIs (fs, path, nypm) and is only
9
- * used by the CLI binary it is NOT exported from the package index.
9
+ * used by the CLI binary; it is NOT exported from the package index.
10
10
  */
11
11
 
12
12
  import * as fs from 'fs/promises'
13
13
  import * as path from 'path'
14
+ import { unzipSync } from 'fflate'
14
15
  import { detectPackageManager, installDependencies } from 'nypm'
15
16
  import type { MarketClient } from './client.js'
16
17
  import type { ResolveResult } from './resolve.js'
17
18
 
19
+ interface PackageJson {
20
+ name?: string
21
+ private?: boolean
22
+ dependencies?: Record<string, string>
23
+ [key: string]: unknown
24
+ }
25
+
18
26
  export interface InstallOptions {
19
27
  /** Project root directory (default: cwd) */
20
28
  cwd?: string
@@ -49,25 +57,27 @@ async function downloadAssets(
49
57
 
50
58
  log(`Downloading ${asset.name}@${asset.version}...`)
51
59
 
52
- const tree = await client.asset.getVersionTree({ name: asset.name, version: asset.version })
60
+ const zip = await client.asset.downloadZip({ name: asset.name, version: asset.version })
61
+ const files = unzipSync(new Uint8Array(await zip.arrayBuffer()))
53
62
 
54
- for (const file of tree) {
55
- const filePath = path.join(destDir, file.path)
63
+ for (const [relativePath, content] of Object.entries(files) as [string, Uint8Array][]) {
64
+ const filePath = path.join(destDir, relativePath)
65
+ if (!isInside(destDir, filePath)) {
66
+ throw new Error(`Zip contains an unsafe path: ${relativePath}`)
67
+ }
56
68
  await fs.mkdir(path.dirname(filePath), { recursive: true })
57
-
58
- const blob = await client.asset.getRawFile({
59
- name: asset.name,
60
- version: asset.version,
61
- path: file.path,
62
- })
63
- const content = Buffer.from(await blob.arrayBuffer())
64
69
  await fs.writeFile(filePath, content)
65
70
  }
66
71
 
67
- log(` ${tree.length} files src/${asset.name}/`)
72
+ log(`Downloaded ${Object.keys(files).length} files to src/${asset.name}/`)
68
73
  }
69
74
  }
70
75
 
76
+ function isInside(root: string, target: string): boolean {
77
+ const relative = path.relative(root, target)
78
+ return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative))
79
+ }
80
+
71
81
  async function installNpmDeps(
72
82
  resolution: ResolveResult,
73
83
  cwd: string,
@@ -78,9 +88,9 @@ async function installNpmDeps(
78
88
 
79
89
  // Read existing package.json
80
90
  const pkgPath = path.join(cwd, 'package.json')
81
- let pkg: any
91
+ let pkg: PackageJson
82
92
  try {
83
- pkg = JSON.parse(await fs.readFile(pkgPath, 'utf-8'))
93
+ pkg = JSON.parse(await fs.readFile(pkgPath, 'utf-8')) as PackageJson
84
94
  } catch {
85
95
  pkg = { name: 'my-project', private: true, dependencies: {} }
86
96
  }
package/src/output.ts ADDED
@@ -0,0 +1,76 @@
1
+ import type { AssetSearchResult } from './contract.js'
2
+
3
+ export function assetVersionRef(name: string, version?: string): string {
4
+ return version ? `${name}@${version}` : name
5
+ }
6
+
7
+ export function assetApproval(approved: boolean): 'approved' | 'unapproved' {
8
+ return approved ? 'approved' : 'unapproved'
9
+ }
10
+
11
+ export function searchSummary(input: {
12
+ query: string
13
+ type: string
14
+ includeUnapproved: boolean
15
+ count: number
16
+ total: number
17
+ }): string {
18
+ const approval = input.includeUnapproved ? 'approved+unapproved' : 'approved'
19
+ const scope = `query="${input.query}" type=${input.type} approval=${approval}`
20
+ if (input.count === 0) {
21
+ return `No results: ${scope}`
22
+ }
23
+ return `Results: ${input.count}/${input.total} ${scope}`
24
+ }
25
+
26
+ export function assetSearchResultLine(
27
+ item: AssetSearchResult,
28
+ opts: { verbose?: boolean } = {},
29
+ ): string {
30
+ const description = item.description
31
+ ? ` | ${compact(item.description, opts.verbose ? 160 : 80)}`
32
+ : ''
33
+ return `- ${assetVersionRef(item.name, item.latestVersion)} | ${item.type} | ${assetApproval(
34
+ item.approved,
35
+ )}${description}`
36
+ }
37
+
38
+ export function installResult(assetVersions: Array<{ name: string; version: string }>): string {
39
+ const label = assetVersions.length === 1 ? 'asset' : 'assets'
40
+ return `Installed ${assetVersions.length} ${label}: ${assetVersions
41
+ .map((asset) => assetVersionRef(asset.name, asset.version))
42
+ .join(', ')}`
43
+ }
44
+
45
+ export function generatedInstallResult(name: string, version: string): string {
46
+ return `Generated and installed ${assetVersionRef(name, version)}`
47
+ }
48
+
49
+ export function uploadResult(name: string, version: string): string {
50
+ return `Uploaded ${assetVersionRef(name, version)}`
51
+ }
52
+
53
+ export function unchangedUploadResult(name: string, version: string): string {
54
+ return `No upload needed: ${assetVersionRef(name, version)} is unchanged`
55
+ }
56
+
57
+ export function previewResult(name: string, version: string, out: string): string {
58
+ return `Saved preview for ${assetVersionRef(name, version)}: ${out}`
59
+ }
60
+
61
+ export function loginResult(email: string, configPath: string): string {
62
+ return `Logged in as ${email} (config: ${configPath})`
63
+ }
64
+
65
+ export function logoutResult(configPath?: string): string {
66
+ return configPath ? `Logged out (removed ${configPath})` : 'Already logged out'
67
+ }
68
+
69
+ export function errorResult(message: string): string {
70
+ return `Error: ${message}`
71
+ }
72
+
73
+ export function compact(value: string, max = 96): string {
74
+ const normalized = value.replace(/\s+/g, ' ').trim()
75
+ return normalized.length > max ? normalized.slice(0, max - 3) + '...' : normalized
76
+ }
package/src/resolve.ts CHANGED
@@ -10,7 +10,7 @@
10
10
 
11
11
  import * as semver from 'semver'
12
12
  import type { MarketClient } from './client.js'
13
- import type { AssetWithVersionsAndTags } from './contract.js'
13
+ import type { AssetSearchResult } from './contract.js'
14
14
 
15
15
  export interface ResolvedAsset {
16
16
  name: string
@@ -41,71 +41,51 @@ export async function resolve(
41
41
  requests: AssetRequest[],
42
42
  opts: { includeUnapproved?: boolean } = {},
43
43
  ): Promise<ResolveResult> {
44
- // constraints[assetName] = list of { range, from } constraints
45
- const constraints: Map<string, { range: string; from: string }[]> = new Map()
46
- // resolved[assetName] = ResolvedAsset
47
- const resolved: Map<string, ResolvedAsset> = new Map()
48
- // cache of fetched metadata
49
- const metaCache: Map<string, AssetWithVersionsAndTags> = new Map()
50
-
51
- // Seed constraints from the requested assets
52
- for (const req of requests) {
53
- addConstraint(constraints, req.name, req.range, '<root>')
44
+ const constraints = new Map<string, { range: string; from: string }[]>()
45
+ const resolved = new Map<string, ResolvedAsset>()
46
+ const metaCache = new Map<string, AssetSearchResult>()
47
+
48
+ for (const request of requests) {
49
+ addConstraint(constraints, request.name, request.range, '<root>')
54
50
  }
55
51
 
56
- // Iteratively resolve until stable
57
52
  let unresolved = getUnresolved(constraints, resolved)
58
53
  while (unresolved.length > 0) {
59
54
  for (const assetName of unresolved) {
60
- const meta = await fetchMeta(client, metaCache, assetName)
55
+ const meta = await fetchMeta(client, metaCache, assetName, opts.includeUnapproved ?? false)
61
56
  if (!meta) {
62
57
  throw new ResolutionError(`Asset "${assetName}" not found.`)
63
58
  }
64
59
 
65
- // Filter to approved versions (unless --unapproved)
66
- const candidates = meta.versions.filter((v) => opts.includeUnapproved || v.approved)
67
-
68
- if (candidates.length === 0) {
60
+ if (!opts.includeUnapproved && !meta.approved) {
69
61
  throw new ResolutionError(
70
62
  `Asset "${assetName}" has no ${opts.includeUnapproved ? '' : 'approved '}versions.`,
71
63
  )
72
64
  }
73
65
 
74
- // Collect all constraints for this asset
75
66
  const assetConstraints = constraints.get(assetName) ?? []
76
-
77
- // Find the best (highest) version that satisfies ALL constraints
78
- const candidateVersions = candidates.map((c) => c.version)
79
- const satisfying = candidateVersions.filter((v) =>
80
- assetConstraints.every((c) => semver.satisfies(v, c.range)),
81
- )
82
-
83
- if (satisfying.length === 0) {
67
+ if (!assetConstraints.every((c) => semver.satisfies(meta.latestVersion, c.range))) {
84
68
  const constraintDesc = assetConstraints
85
69
  .map((c) => ` ${c.range} (from ${c.from})`)
86
70
  .join('\n')
87
71
  throw new ResolutionError(
88
72
  `No version of "${assetName}" satisfies all constraints:\n${constraintDesc}\n` +
89
- `Available${opts.includeUnapproved ? '' : ' approved'}: ${candidateVersions.join(', ')}`,
73
+ `Available${opts.includeUnapproved ? '' : ' approved'}: ${meta.latestVersion}`,
90
74
  )
91
75
  }
92
76
 
93
- const best = semver.maxSatisfying(satisfying, '*')!
94
- const versionMeta = candidates.find((c) => c.version === best)!
95
-
96
- const npmDeps: Record<string, string> = JSON.parse(versionMeta.npmDependencies)
97
- const assetDeps: Record<string, string> = JSON.parse(versionMeta.assetDependencies)
77
+ const npmDeps: Record<string, string> = JSON.parse(meta.npmDependencies)
78
+ const assetDeps: Record<string, string> = JSON.parse(meta.assetDependencies)
98
79
 
99
80
  resolved.set(assetName, {
100
81
  name: assetName,
101
- version: best,
82
+ version: meta.latestVersion,
102
83
  npmDependencies: npmDeps,
103
84
  assetDependencies: assetDeps,
104
85
  })
105
86
 
106
- // Add transitive asset dependency constraints
107
87
  for (const [depName, depRange] of Object.entries(assetDeps)) {
108
- addConstraint(constraints, depName, depRange, `${assetName}@${best}`)
88
+ addConstraint(constraints, depName, depRange, `${assetName}@${meta.latestVersion}`)
109
89
  }
110
90
  }
111
91
 
@@ -155,11 +135,15 @@ function getUnresolved(
155
135
 
156
136
  async function fetchMeta(
157
137
  client: MarketClient['asset'],
158
- cache: Map<string, AssetWithVersionsAndTags>,
138
+ cache: Map<string, AssetSearchResult>,
159
139
  name: string,
160
- ): Promise<AssetWithVersionsAndTags | null> {
140
+ includeUnapproved: boolean,
141
+ ): Promise<AssetSearchResult | null> {
161
142
  if (cache.has(name)) return cache.get(name)!
162
- const meta = await client.getByName({ name })
143
+ const meta = await client.exact({
144
+ name,
145
+ includeUnapproved,
146
+ })
163
147
  if (meta) cache.set(name, meta)
164
148
  return meta
165
149
  }
package/src/schemas.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { z } from 'zod'
2
2
 
3
- export const ASSET_TYPES = ['generic', 'model', 'hdri', 'material', 'music'] as const
3
+ export const ASSET_TYPES = ['model'] as const
4
4
  export type AssetType = (typeof ASSET_TYPES)[number]
5
5
 
6
6
  export const assetTypeSchema = z.enum(ASSET_TYPES)
@@ -28,8 +28,29 @@ export const npmDependenciesSchema = z.record(z.string(), z.string()).default({}
28
28
 
29
29
  export const assetDependenciesSchema = z.record(z.string(), z.string()).default({})
30
30
 
31
- export const uploadGenericSchema = z.object({
31
+ export const updateProfileSchema = z.object({
32
+ name: z.string().min(1).max(100).optional(),
33
+ image: z.string().url().optional(),
34
+ })
35
+
36
+ export const listAssetsSchema = z.object({
37
+ page: z.number().int().min(1).default(1),
38
+ limit: z.number().int().min(1).max(100).default(20),
39
+ type: assetTypeSchema.optional(),
40
+ query: z.string().max(200).optional(),
41
+ includeUnapproved: z.boolean().default(false),
42
+ sort: z.enum(['newest', 'alphabetical', 'relevance']).default('newest'),
43
+ })
44
+
45
+ export const exactAssetSchema = z.object({
32
46
  name: assetNameSchema,
47
+ type: assetTypeSchema.optional(),
48
+ includeUnapproved: z.boolean().default(false),
49
+ })
50
+
51
+ export const uploadZipSchema = z.object({
52
+ name: assetNameSchema,
53
+ type: assetTypeSchema,
33
54
  version: semverSchema,
34
55
  description: z.string().max(1000).optional(),
35
56
  npmDependencies: npmDependenciesSchema,
@@ -37,34 +58,12 @@ export const uploadGenericSchema = z.object({
37
58
  tags: z.array(z.string()).default([]),
38
59
  })
39
60
 
40
- export const uploadTypedSchema = z.object({
61
+ export const downloadZipSchema = z.object({
41
62
  name: assetNameSchema,
42
63
  version: semverSchema,
43
- description: z.string().max(1000).optional(),
44
- tags: z.array(z.string()).default([]),
45
- })
46
-
47
- export const uploadMaterialSchema = uploadTypedSchema.extend({
48
- properties: z.object({
49
- color: z.string().default('#ffffff'),
50
- roughness: z.number().min(0).max(1).default(0.5),
51
- metalness: z.number().min(0).max(1).default(0),
52
- normalScale: z.number().min(0).max(2).default(1),
53
- emissive: z.string().default('#000000'),
54
- emissiveIntensity: z.number().min(0).max(10).default(0),
55
- }),
56
- })
57
-
58
- export const updateProfileSchema = z.object({
59
- name: z.string().min(1).max(100).optional(),
60
- image: z.string().url().optional(),
61
64
  })
62
65
 
63
- export const listAssetsSchema = z.object({
64
- page: z.number().int().min(1).default(1),
65
- limit: z.number().int().min(1).max(100).default(20),
66
+ export const generateAssetSchema = z.object({
67
+ description: z.string().min(3).max(1000),
66
68
  type: assetTypeSchema.optional(),
67
- tag: z.string().optional(),
68
- search: z.string().max(200).optional(),
69
- sort: z.enum(['newest', 'alphabetical', 'relevance']).default('newest'),
70
69
  })
package/tsconfig.json CHANGED
@@ -2,7 +2,8 @@
2
2
  "extends": "../../tsconfig.base.json",
3
3
  "compilerOptions": {
4
4
  "outDir": "dist",
5
- "rootDir": "src"
5
+ "rootDir": "src",
6
+ "types": ["node"]
6
7
  },
7
8
  "include": ["src"]
8
9
  }
@@ -1,10 +0,0 @@
1
- export interface LoginOptions {
2
- baseUrl: string;
3
- timeoutMs?: number;
4
- }
5
- /**
6
- * Spins up a localhost HTTP server, opens the browser to {baseUrl}/cli-auth,
7
- * waits for the web app to redirect back with the key, saves it to config.
8
- */
9
- export declare function login(opts: LoginOptions): Promise<void>;
10
- //# sourceMappingURL=login.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"login.d.ts","sourceRoot":"","sources":["../../src/commands/login.ts"],"names":[],"mappings":"AAQA,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,CAAA;IACf,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED;;;GAGG;AACH,wBAAsB,KAAK,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAiC7D"}
@@ -1,92 +0,0 @@
1
- import * as http from 'http';
2
- import chalk from 'chalk';
3
- import open from 'open';
4
- import ora from 'ora';
5
- import { createMarketClient } from '../client.js';
6
- import { saveConfig, getConfigPath } from '../config.js';
7
- /**
8
- * Spins up a localhost HTTP server, opens the browser to {baseUrl}/cli-auth,
9
- * waits for the web app to redirect back with the key, saves it to config.
10
- */
11
- export async function login(opts) {
12
- const timeoutMs = opts.timeoutMs ?? 120_000;
13
- const state = crypto.randomUUID();
14
- const { port, waitForKey, close } = await startCallbackServer(state, timeoutMs);
15
- const authUrl = new URL('/cli-auth', opts.baseUrl);
16
- authUrl.searchParams.set('state', state);
17
- authUrl.searchParams.set('callback', `http://127.0.0.1:${port}/callback`);
18
- console.log(chalk.dim(`If your browser doesn't open, visit:\n ${authUrl.toString()}`));
19
- await open(authUrl.toString()).catch(() => { });
20
- const spinner = ora('Waiting for browser approval…').start();
21
- try {
22
- const key = await waitForKey;
23
- spinner.text = 'Verifying key…';
24
- const verifyClient = createMarketClient({ baseUrl: opts.baseUrl, apiKey: key });
25
- const profile = await verifyClient.user.getProfile();
26
- if (!profile) {
27
- throw new Error('Key was not accepted by the server.');
28
- }
29
- await saveConfig({ apiKey: key, baseUrl: opts.baseUrl });
30
- spinner.succeed(`Logged in as ${chalk.cyan(profile.email)}`);
31
- console.log(chalk.dim(` Key saved to ${getConfigPath()}`));
32
- }
33
- catch (err) {
34
- spinner.fail(err instanceof Error ? err.message : String(err));
35
- throw err;
36
- }
37
- finally {
38
- close();
39
- }
40
- }
41
- async function startCallbackServer(expectedState, timeoutMs) {
42
- let resolveKey;
43
- let rejectKey;
44
- const waitForKey = new Promise((resolve, reject) => {
45
- resolveKey = resolve;
46
- rejectKey = reject;
47
- });
48
- const server = http.createServer((req, res) => {
49
- if (!req.url) {
50
- res.writeHead(400).end();
51
- return;
52
- }
53
- const url = new URL(req.url, 'http://localhost');
54
- if (url.pathname !== '/callback') {
55
- res.writeHead(404).end();
56
- return;
57
- }
58
- const receivedState = url.searchParams.get('state');
59
- const receivedKey = url.searchParams.get('key');
60
- if (receivedState !== expectedState) {
61
- res.writeHead(400, { 'Content-Type': 'text/html' });
62
- res.end('<h1>Invalid state</h1><p>You can close this window.</p>');
63
- rejectKey(new Error('Invalid state parameter from callback.'));
64
- return;
65
- }
66
- if (!receivedKey) {
67
- res.writeHead(400, { 'Content-Type': 'text/html' });
68
- res.end('<h1>Missing key</h1><p>You can close this window.</p>');
69
- rejectKey(new Error('Callback did not include a key.'));
70
- return;
71
- }
72
- res.writeHead(200, { 'Content-Type': 'text/html' });
73
- res.end('<h1>Logged in!</h1><p>You can close this window.</p>');
74
- resolveKey(receivedKey);
75
- });
76
- await new Promise((resolve, reject) => {
77
- server.once('error', reject);
78
- server.listen(0, '127.0.0.1', () => resolve());
79
- });
80
- const address = server.address();
81
- if (!address)
82
- throw new Error('Failed to bind local callback server.');
83
- const timer = setTimeout(() => {
84
- rejectKey(new Error(`Timed out after ${timeoutMs / 1000}s waiting for browser approval.`));
85
- }, timeoutMs);
86
- const close = () => {
87
- clearTimeout(timer);
88
- server.close();
89
- };
90
- return { port: address.port, waitForKey, close };
91
- }
92
- //# sourceMappingURL=login.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"login.js","sourceRoot":"","sources":["../../src/commands/login.ts"],"names":[],"mappings":"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"}
@@ -1,19 +0,0 @@
1
- import { z } from 'zod';
2
- export declare const internalContract: {
3
- buildUpload: import("@orpc/contract").ContractProcedureBuilderWithInputOutput<z.ZodObject<{
4
- key: z.ZodString;
5
- content: z.ZodString;
6
- contentType: z.ZodString;
7
- }, z.core.$strip>, z.ZodObject<{
8
- ok: z.ZodBoolean;
9
- }, z.core.$strip>, Record<never, never>, Record<never, never>>;
10
- buildComplete: import("@orpc/contract").ContractProcedureBuilderWithInputOutput<z.ZodObject<{
11
- assetName: z.ZodString;
12
- version: z.ZodString;
13
- buildOutputKey: z.ZodString;
14
- }, z.core.$strip>, z.ZodObject<{
15
- ok: z.ZodBoolean;
16
- }, z.core.$strip>, Record<never, never>, Record<never, never>>;
17
- };
18
- export type InternalContract = typeof internalContract;
19
- //# sourceMappingURL=internal-contract.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"internal-contract.d.ts","sourceRoot":"","sources":["../src/internal-contract.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;CAoB5B,CAAA;AAED,MAAM,MAAM,gBAAgB,GAAG,OAAO,gBAAgB,CAAA"}
@@ -1,19 +0,0 @@
1
- import { oc } from '@orpc/contract';
2
- import { z } from 'zod';
3
- export const internalContract = {
4
- buildUpload: oc
5
- .input(z.object({
6
- key: z.string(),
7
- content: z.string(), // base64-encoded
8
- contentType: z.string(),
9
- }))
10
- .output(z.object({ ok: z.boolean() })),
11
- buildComplete: oc
12
- .input(z.object({
13
- assetName: z.string(),
14
- version: z.string(),
15
- buildOutputKey: z.string(),
16
- }))
17
- .output(z.object({ ok: z.boolean() })),
18
- };
19
- //# sourceMappingURL=internal-contract.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"internal-contract.js","sourceRoot":"","sources":["../src/internal-contract.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,EAAE,MAAM,gBAAgB,CAAA;AACnC,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB,MAAM,CAAC,MAAM,gBAAgB,GAAG;IAC9B,WAAW,EAAE,EAAE;SACZ,KAAK,CACJ,CAAC,CAAC,MAAM,CAAC;QACP,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE;QACf,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,iBAAiB;QACtC,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE;KACxB,CAAC,CACH;SACA,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;IAExC,aAAa,EAAE,EAAE;SACd,KAAK,CACJ,CAAC,CAAC,MAAM,CAAC;QACP,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE;QACrB,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE;QACnB,cAAc,EAAE,CAAC,CAAC,MAAM,EAAE;KAC3B,CAAC,CACH;SACA,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;CACzC,CAAA"}
@@ -1,113 +0,0 @@
1
- import * as http from 'http'
2
- import type { AddressInfo } from 'net'
3
- import chalk from 'chalk'
4
- import open from 'open'
5
- import ora from 'ora'
6
- import { createMarketClient } from '../client.js'
7
- import { saveConfig, getConfigPath } from '../config.js'
8
-
9
- export interface LoginOptions {
10
- baseUrl: string
11
- timeoutMs?: number
12
- }
13
-
14
- /**
15
- * Spins up a localhost HTTP server, opens the browser to {baseUrl}/cli-auth,
16
- * waits for the web app to redirect back with the key, saves it to config.
17
- */
18
- export async function login(opts: LoginOptions): Promise<void> {
19
- const timeoutMs = opts.timeoutMs ?? 120_000
20
- const state = crypto.randomUUID()
21
-
22
- const { port, waitForKey, close } = await startCallbackServer(state, timeoutMs)
23
-
24
- const authUrl = new URL('/cli-auth', opts.baseUrl)
25
- authUrl.searchParams.set('state', state)
26
- authUrl.searchParams.set('callback', `http://127.0.0.1:${port}/callback`)
27
-
28
- console.log(chalk.dim(`If your browser doesn't open, visit:\n ${authUrl.toString()}`))
29
- await open(authUrl.toString()).catch(() => {})
30
-
31
- const spinner = ora('Waiting for browser approval…').start()
32
- try {
33
- const key = await waitForKey
34
-
35
- spinner.text = 'Verifying key…'
36
- const verifyClient = createMarketClient({ baseUrl: opts.baseUrl, apiKey: key })
37
- const profile = await verifyClient.user.getProfile()
38
- if (!profile) {
39
- throw new Error('Key was not accepted by the server.')
40
- }
41
-
42
- await saveConfig({ apiKey: key, baseUrl: opts.baseUrl })
43
- spinner.succeed(`Logged in as ${chalk.cyan(profile.email)}`)
44
- console.log(chalk.dim(` Key saved to ${getConfigPath()}`))
45
- } catch (err) {
46
- spinner.fail(err instanceof Error ? err.message : String(err))
47
- throw err
48
- } finally {
49
- close()
50
- }
51
- }
52
-
53
- async function startCallbackServer(
54
- expectedState: string,
55
- timeoutMs: number,
56
- ): Promise<{ port: number; waitForKey: Promise<string>; close: () => void }> {
57
- let resolveKey!: (k: string) => void
58
- let rejectKey!: (e: Error) => void
59
- const waitForKey = new Promise<string>((resolve, reject) => {
60
- resolveKey = resolve
61
- rejectKey = reject
62
- })
63
-
64
- const server = http.createServer((req, res) => {
65
- if (!req.url) {
66
- res.writeHead(400).end()
67
- return
68
- }
69
- const url = new URL(req.url, 'http://localhost')
70
- if (url.pathname !== '/callback') {
71
- res.writeHead(404).end()
72
- return
73
- }
74
- const receivedState = url.searchParams.get('state')
75
- const receivedKey = url.searchParams.get('key')
76
-
77
- if (receivedState !== expectedState) {
78
- res.writeHead(400, { 'Content-Type': 'text/html' })
79
- res.end('<h1>Invalid state</h1><p>You can close this window.</p>')
80
- rejectKey(new Error('Invalid state parameter from callback.'))
81
- return
82
- }
83
- if (!receivedKey) {
84
- res.writeHead(400, { 'Content-Type': 'text/html' })
85
- res.end('<h1>Missing key</h1><p>You can close this window.</p>')
86
- rejectKey(new Error('Callback did not include a key.'))
87
- return
88
- }
89
-
90
- res.writeHead(200, { 'Content-Type': 'text/html' })
91
- res.end('<h1>Logged in!</h1><p>You can close this window.</p>')
92
- resolveKey(receivedKey)
93
- })
94
-
95
- await new Promise<void>((resolve, reject) => {
96
- server.once('error', reject)
97
- server.listen(0, '127.0.0.1', () => resolve())
98
- })
99
-
100
- const address = server.address() as AddressInfo | null
101
- if (!address) throw new Error('Failed to bind local callback server.')
102
-
103
- const timer = setTimeout(() => {
104
- rejectKey(new Error(`Timed out after ${timeoutMs / 1000}s waiting for browser approval.`))
105
- }, timeoutMs)
106
-
107
- const close = () => {
108
- clearTimeout(timer)
109
- server.close()
110
- }
111
-
112
- return { port: address.port, waitForKey, close }
113
- }
@@ -1,26 +0,0 @@
1
- import { oc } from '@orpc/contract'
2
- import { z } from 'zod'
3
-
4
- export const internalContract = {
5
- buildUpload: oc
6
- .input(
7
- z.object({
8
- key: z.string(),
9
- content: z.string(), // base64-encoded
10
- contentType: z.string(),
11
- }),
12
- )
13
- .output(z.object({ ok: z.boolean() })),
14
-
15
- buildComplete: oc
16
- .input(
17
- z.object({
18
- assetName: z.string(),
19
- version: z.string(),
20
- buildOutputKey: z.string(),
21
- }),
22
- )
23
- .output(z.object({ ok: z.boolean() })),
24
- }
25
-
26
- export type InternalContract = typeof internalContract