forked from
npmx.dev/npmx.dev
[READ-ONLY]
a fast, modern browser for the npm registry
1#!/usr/bin/env node
2/**
3 * Mock connector CLI — starts a pre-populated mock server for developing
4 * authenticated features without a real npm account.
5 */
6
7import process from 'node:process'
8import crypto from 'node:crypto'
9import { styleText } from 'node:util'
10import * as p from '@clack/prompts'
11import { defineCommand, runMain } from 'citty'
12import {
13 MockConnectorStateManager,
14 createMockConnectorState,
15 type MockConnectorConfig,
16} from './mock-state.ts'
17import { MockConnectorServer } from './mock-app.ts'
18
19const DEFAULT_PORT = 31415
20const DEV_FRONTEND_URL = 'http://127.0.0.1:3000/'
21const PROD_FRONTEND_URL = 'https://npmx.dev/'
22
23function generateToken(): string {
24 return crypto.randomBytes(16).toString('hex')
25}
26
27/**
28 * Pre-populate with sample data using real npm orgs so the registry
29 * API calls don't 404. Members/teams are fictional.
30 */
31function populateDefaultData(stateManager: MockConnectorStateManager): void {
32 const npmUser = stateManager.config.npmUser
33
34 stateManager.setOrgData('@nuxt', {
35 users: {
36 [npmUser]: 'owner',
37 danielroe: 'owner',
38 pi0: 'admin',
39 antfu: 'developer',
40 },
41 teams: ['core', 'docs', 'triage'],
42 teamMembers: {
43 core: [npmUser, 'danielroe', 'pi0'],
44 docs: ['antfu'],
45 triage: ['pi0', 'antfu'],
46 },
47 })
48
49 stateManager.setOrgData('@unjs', {
50 users: {
51 [npmUser]: 'admin',
52 pi0: 'owner',
53 },
54 teams: ['maintainers'],
55 teamMembers: {
56 maintainers: [npmUser, 'pi0'],
57 },
58 })
59
60 stateManager.setUserOrgs(['nuxt', 'unjs'])
61
62 stateManager.setPackageData('@nuxt/kit', {
63 collaborators: {
64 [npmUser]: 'read-write',
65 'danielroe': 'read-write',
66 'nuxt:core': 'read-write',
67 'nuxt:docs': 'read-only',
68 },
69 })
70 stateManager.setPackageData('@nuxt/schema', {
71 collaborators: {
72 [npmUser]: 'read-write',
73 'nuxt:core': 'read-write',
74 },
75 })
76 stateManager.setPackageData('@unjs/nitro', {
77 collaborators: {
78 [npmUser]: 'read-write',
79 'pi0': 'read-write',
80 'unjs:maintainers': 'read-write',
81 },
82 })
83
84 stateManager.setUserPackages({
85 '@nuxt/kit': 'read-write',
86 '@nuxt/schema': 'read-write',
87 '@unjs/nitro': 'read-write',
88 })
89}
90
91const main = defineCommand({
92 meta: {
93 name: 'npmx-connector-mock',
94 version: '0.0.1',
95 description: 'Mock connector for npmx.dev development and testing',
96 },
97 args: {
98 port: {
99 type: 'string',
100 description: 'Port to listen on',
101 default: String(DEFAULT_PORT),
102 },
103 user: {
104 type: 'string',
105 description: 'Simulated npm username',
106 default: 'mock-user',
107 },
108 empty: {
109 type: 'boolean',
110 description: 'Start with empty state (no pre-populated data)',
111 default: false,
112 },
113 },
114 async run({ args }) {
115 const port = Number.parseInt(args.port as string, 10) || DEFAULT_PORT
116 const npmUser = args.user as string
117 const empty = args.empty as boolean
118 const frontendUrl = process.env.NPMX_CLI_DEV === 'true' ? DEV_FRONTEND_URL : PROD_FRONTEND_URL
119
120 p.intro(styleText(['bgMagenta', 'white'], ' npmx mock connector '))
121
122 const token = generateToken()
123 const config: MockConnectorConfig = {
124 token,
125 npmUser,
126 avatar: null,
127 port,
128 }
129 const stateManager = new MockConnectorStateManager(createMockConnectorState(config))
130
131 if (!empty) {
132 populateDefaultData(stateManager)
133 p.log.info(`Pre-populated with sample data for ${styleText('cyan', npmUser)}`)
134 p.log.info(styleText('dim', ` Orgs: @nuxt (4 members, 3 teams), @unjs (2 members, 1 team)`))
135 p.log.info(styleText('dim', ` Packages: @nuxt/kit, @nuxt/schema, @unjs/nitro`))
136 } else {
137 p.log.info('Starting with empty state')
138 }
139
140 stateManager.connect(token)
141 const server = new MockConnectorServer(stateManager)
142
143 try {
144 await server.start()
145 } catch (error) {
146 p.log.error(error instanceof Error ? error.message : 'Failed to start mock connector server')
147 process.exit(1)
148 }
149
150 const connectUrl = `${frontendUrl}?token=${token}&port=${port}`
151
152 p.note(
153 [
154 `Open: ${styleText(['bold', 'underline', 'cyan'], connectUrl)}`,
155 '',
156 styleText('dim', `Or paste token manually: ${token}`),
157 '',
158 styleText('dim', `User: ${npmUser} | Port: ${port}`),
159 styleText('dim', 'Operations will succeed immediately (no real npm calls)'),
160 ].join('\n'),
161 'Click to connect',
162 )
163
164 p.log.info('Waiting for connection... (Press Ctrl+C to stop)')
165 },
166})
167
168runMain(main)