@livestore/cli 0.4.0-dev.8 → 0.4.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.
- package/dist/.tsbuildinfo +1 -0
- package/dist/__tests__/fixtures/mock-config.d.ts +65 -0
- package/dist/__tests__/fixtures/mock-config.d.ts.map +1 -0
- package/dist/__tests__/fixtures/mock-config.js +94 -0
- package/dist/__tests__/fixtures/mock-config.js.map +1 -0
- package/dist/__tests__/sync-operations.test.d.ts +2 -0
- package/dist/__tests__/sync-operations.test.d.ts.map +1 -0
- package/dist/__tests__/sync-operations.test.js +167 -0
- package/dist/__tests__/sync-operations.test.js.map +1 -0
- package/dist/bin.js.map +1 -1
- package/dist/cli.d.ts +16 -2
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +3 -4
- package/dist/cli.js.map +1 -1
- package/dist/commands/import-export.d.ts +34 -0
- package/dist/commands/import-export.d.ts.map +1 -0
- package/dist/commands/import-export.js +135 -0
- package/dist/commands/import-export.js.map +1 -0
- package/dist/commands/mcp-coach.d.ts +13 -5
- package/dist/commands/mcp-coach.d.ts.map +1 -1
- package/dist/commands/mcp-coach.js +41 -45
- package/dist/commands/mcp-coach.js.map +1 -1
- package/dist/commands/mcp-tool-handlers.d.ts +6 -0
- package/dist/commands/mcp-tool-handlers.d.ts.map +1 -0
- package/dist/commands/{mcp-tools.js → mcp-tool-handlers.js} +79 -45
- package/dist/commands/mcp-tool-handlers.js.map +1 -0
- package/dist/commands/mcp-tools-defs.d.ts +133 -0
- package/dist/commands/mcp-tools-defs.d.ts.map +1 -0
- package/dist/commands/mcp-tools-defs.js +278 -0
- package/dist/commands/mcp-tools-defs.js.map +1 -0
- package/dist/commands/mcp.d.ts.map +1 -1
- package/dist/commands/mcp.js +2 -1
- package/dist/commands/mcp.js.map +1 -1
- package/dist/commands/new-project.d.ts +9 -2
- package/dist/commands/new-project.d.ts.map +1 -1
- package/dist/commands/new-project.js +72 -32
- package/dist/commands/new-project.js.map +1 -1
- package/dist/mcp-runtime/runtime.d.ts +48 -0
- package/dist/mcp-runtime/runtime.d.ts.map +1 -0
- package/dist/mcp-runtime/runtime.js +107 -0
- package/dist/mcp-runtime/runtime.js.map +1 -0
- package/dist/module-loader.d.ts +22 -0
- package/dist/module-loader.d.ts.map +1 -0
- package/dist/module-loader.js +75 -0
- package/dist/module-loader.js.map +1 -0
- package/dist/package-manager.d.ts +38 -0
- package/dist/package-manager.d.ts.map +1 -0
- package/dist/package-manager.js +33 -0
- package/dist/package-manager.js.map +1 -0
- package/dist/package-manager.test.d.ts +2 -0
- package/dist/package-manager.test.d.ts.map +1 -0
- package/dist/package-manager.test.js +57 -0
- package/dist/package-manager.test.js.map +1 -0
- package/dist/sync-operations.d.ts +121 -0
- package/dist/sync-operations.d.ts.map +1 -0
- package/dist/sync-operations.js +180 -0
- package/dist/sync-operations.js.map +1 -0
- package/package.json +71 -15
- package/src/__tests__/fixtures/mock-config.ts +112 -0
- package/src/__tests__/sync-operations.test.ts +232 -0
- package/src/bin.ts +1 -0
- package/src/cli.ts +4 -5
- package/src/commands/import-export.ts +283 -0
- package/src/commands/mcp-coach.ts +67 -67
- package/src/commands/{mcp-tools.ts → mcp-tool-handlers.ts} +93 -57
- package/src/commands/mcp-tools-defs.ts +318 -0
- package/src/commands/mcp.ts +3 -2
- package/src/commands/new-project.ts +90 -36
- package/src/mcp-runtime/runtime.ts +147 -0
- package/src/module-loader.ts +94 -0
- package/src/package-manager.test.ts +66 -0
- package/src/package-manager.ts +44 -0
- package/src/sync-operations.ts +360 -0
- package/dist/commands/mcp-tools.d.ts +0 -58
- package/dist/commands/mcp-tools.d.ts.map +0 -1
- package/dist/commands/mcp-tools.js.map +0 -1
- package/dist/tsconfig.tsbuildinfo +0 -1
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { makeAdapter as makeNodeAdapter } from '@livestore/adapter-node'
|
|
2
|
+
import { UnknownError } from '@livestore/common'
|
|
3
|
+
import { LiveStoreEvent, SystemTables } from '@livestore/common/schema'
|
|
4
|
+
import type { Store } from '@livestore/livestore'
|
|
5
|
+
import { createStorePromise } from '@livestore/livestore'
|
|
6
|
+
import { Effect, FetchHttpClient, Layer, Option, Schema } from '@livestore/utils/effect'
|
|
7
|
+
import { PlatformNode } from '@livestore/utils/node'
|
|
8
|
+
|
|
9
|
+
import { loadModuleConfig } from '../module-loader.ts'
|
|
10
|
+
|
|
11
|
+
/** Currently connected store */
|
|
12
|
+
let store: Store<any> | undefined
|
|
13
|
+
|
|
14
|
+
/** Layer providing FileSystem and HttpClient for module loading */
|
|
15
|
+
const ModuleLoaderLayer = Layer.mergeAll(PlatformNode.NodeFileSystem.layer, FetchHttpClient.layer)
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Dynamically imports a module that exports a `makeStore({ storeId }): Promise<Store>` function,
|
|
19
|
+
* calls it with the provided storeId, and caches the Store instance for subsequent tool calls.
|
|
20
|
+
*/
|
|
21
|
+
export const init = ({
|
|
22
|
+
configPath,
|
|
23
|
+
storeId,
|
|
24
|
+
clientId,
|
|
25
|
+
sessionId,
|
|
26
|
+
}: {
|
|
27
|
+
configPath: string
|
|
28
|
+
storeId: string
|
|
29
|
+
clientId?: string
|
|
30
|
+
sessionId?: string
|
|
31
|
+
}): Effect.Effect<Store<any>, UnknownError> =>
|
|
32
|
+
Effect.gen(function* () {
|
|
33
|
+
if (storeId === '' || typeof storeId !== 'string') {
|
|
34
|
+
return yield* UnknownError.make({ cause: new Error('Invalid storeId: expected a non-empty string') })
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const { schema, syncBackendConstructor, syncPayloadSchema, syncPayload } = yield* loadModuleConfig({ configPath })
|
|
38
|
+
|
|
39
|
+
// Build Node adapter internally
|
|
40
|
+
const adapter = makeNodeAdapter({
|
|
41
|
+
storage: { type: 'in-memory' },
|
|
42
|
+
...(clientId !== undefined && clientId !== '' ? { clientId } : {}),
|
|
43
|
+
...(sessionId !== undefined && sessionId !== '' ? { sessionId } : {}),
|
|
44
|
+
sync: {
|
|
45
|
+
backend: syncBackendConstructor,
|
|
46
|
+
initialSyncOptions: { _tag: 'Blocking', timeout: 5000 },
|
|
47
|
+
onSyncError: 'shutdown',
|
|
48
|
+
},
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
// Create the store
|
|
52
|
+
const s = yield* Effect.promise(() =>
|
|
53
|
+
createStorePromise({
|
|
54
|
+
schema,
|
|
55
|
+
storeId,
|
|
56
|
+
adapter,
|
|
57
|
+
disableDevtools: true,
|
|
58
|
+
syncPayload,
|
|
59
|
+
syncPayloadSchema,
|
|
60
|
+
}),
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
// Replace existing store if any
|
|
64
|
+
if (store !== undefined) {
|
|
65
|
+
yield* Effect.promise(async () => {
|
|
66
|
+
try {
|
|
67
|
+
await store!.shutdownPromise()
|
|
68
|
+
} catch {}
|
|
69
|
+
})
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
store = s
|
|
73
|
+
return store
|
|
74
|
+
}).pipe(Effect.provide(ModuleLoaderLayer), Effect.withSpan('mcp-runtime:init'))
|
|
75
|
+
|
|
76
|
+
export const getStore = Effect.sync(() => Option.fromNullable(store))
|
|
77
|
+
|
|
78
|
+
export const status = Effect.gen(function* () {
|
|
79
|
+
const opt = yield* getStore
|
|
80
|
+
if (opt._tag === 'None') {
|
|
81
|
+
return {
|
|
82
|
+
_tag: 'disconnected' as const,
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
const s = opt.value
|
|
86
|
+
const tableCounts = Array.from(s.schema.state.sqlite.tables.keys())
|
|
87
|
+
.filter((name) => !SystemTables.isStateSystemTable(name))
|
|
88
|
+
.reduce(
|
|
89
|
+
(acc, name) => {
|
|
90
|
+
acc[name] = s.query(s.schema.state.sqlite.tables.get(name)!.count())
|
|
91
|
+
return acc
|
|
92
|
+
},
|
|
93
|
+
{} as Record<string, number>,
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
_tag: 'connected' as const,
|
|
98
|
+
storeId: s.storeId,
|
|
99
|
+
clientId: s.clientId,
|
|
100
|
+
sessionId: s.sessionId,
|
|
101
|
+
tableCounts,
|
|
102
|
+
}
|
|
103
|
+
}).pipe(Effect.withSpan('mcp-runtime:status'))
|
|
104
|
+
|
|
105
|
+
export const query = Effect.fn('mcp-runtime:query')(function* ({
|
|
106
|
+
sql,
|
|
107
|
+
bindValues,
|
|
108
|
+
}: {
|
|
109
|
+
sql: string
|
|
110
|
+
bindValues?: readonly any[] | Record<string, unknown>
|
|
111
|
+
}) {
|
|
112
|
+
const opt = yield* getStore
|
|
113
|
+
if (opt._tag === 'None') {
|
|
114
|
+
return yield* Effect.dieMessage('LiveStore not connected. Call livestore_instance_connect first.')
|
|
115
|
+
}
|
|
116
|
+
const s = opt.value
|
|
117
|
+
|
|
118
|
+
const rows = s.query<Array<Record<string, unknown>>>({ query: sql, bindValues: (bindValues as any) ?? [] })
|
|
119
|
+
const jsonRows = rows.map((r) => Object.fromEntries(Object.entries(r).map(([k, v]) => [k, v as Schema.JsonValue])))
|
|
120
|
+
return { rows: jsonRows, rowCount: jsonRows.length }
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
export const commit = Effect.fn('mcp-runtime:commit')(function* ({
|
|
124
|
+
events,
|
|
125
|
+
}: {
|
|
126
|
+
events: ReadonlyArray<{ name: string; args: Schema.JsonValue }>
|
|
127
|
+
}) {
|
|
128
|
+
const opt = yield* getStore
|
|
129
|
+
if (opt._tag === 'None') {
|
|
130
|
+
return yield* Effect.dieMessage('LiveStore not connected. Call livestore_instance_connect first.')
|
|
131
|
+
}
|
|
132
|
+
const s = opt.value
|
|
133
|
+
const InputEventSchema = LiveStoreEvent.Input.makeSchema(s.schema) as Schema.Schema<any>
|
|
134
|
+
const decoded = events.map((e) => Schema.decodeSync(InputEventSchema)(e))
|
|
135
|
+
s.commit(...decoded)
|
|
136
|
+
return { committed: decoded.length }
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
export const disconnect = Effect.promise(async () => {
|
|
140
|
+
if (store !== undefined) {
|
|
141
|
+
try {
|
|
142
|
+
await store.shutdownPromise()
|
|
143
|
+
} catch {}
|
|
144
|
+
store = undefined
|
|
145
|
+
}
|
|
146
|
+
return { _tag: 'disconnected' as const }
|
|
147
|
+
}).pipe(Effect.withSpan('mcp-runtime:disconnect'))
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared module loading utility for CLI and MCP.
|
|
3
|
+
* Loads and validates user config modules that export schema, syncBackend, and optional syncPayload.
|
|
4
|
+
*/
|
|
5
|
+
import path from 'node:path'
|
|
6
|
+
import { pathToFileURL } from 'node:url'
|
|
7
|
+
|
|
8
|
+
import type { SyncBackend } from '@livestore/common'
|
|
9
|
+
import { UnknownError } from '@livestore/common'
|
|
10
|
+
import { isLiveStoreSchema, type LiveStoreSchema } from '@livestore/common/schema'
|
|
11
|
+
import { shouldNeverHappen } from '@livestore/utils'
|
|
12
|
+
import { Effect, FileSystem, Schema } from '@livestore/utils/effect'
|
|
13
|
+
|
|
14
|
+
export interface ModuleConfig {
|
|
15
|
+
schema: LiveStoreSchema
|
|
16
|
+
syncBackendConstructor: SyncBackend.SyncBackendConstructor
|
|
17
|
+
syncPayloadSchema: Schema.Schema<any>
|
|
18
|
+
syncPayload: unknown
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Loads and validates a user config module.
|
|
23
|
+
* The module must export:
|
|
24
|
+
* - `schema`: A valid LiveStore schema
|
|
25
|
+
* - `syncBackend`: A sync backend constructor function
|
|
26
|
+
* - `syncPayloadSchema` (optional): Schema for validating syncPayload
|
|
27
|
+
* - `syncPayload` (optional): Payload data for the sync backend
|
|
28
|
+
*/
|
|
29
|
+
export const loadModuleConfig = ({
|
|
30
|
+
configPath,
|
|
31
|
+
}: {
|
|
32
|
+
configPath: string
|
|
33
|
+
}): Effect.Effect<ModuleConfig, UnknownError, FileSystem.FileSystem> =>
|
|
34
|
+
Effect.gen(function* () {
|
|
35
|
+
const abs = path.isAbsolute(configPath) === true ? configPath : path.resolve(process.cwd(), configPath)
|
|
36
|
+
|
|
37
|
+
const fs = yield* FileSystem.FileSystem
|
|
38
|
+
const exists = yield* fs.exists(abs).pipe(UnknownError.mapToUnknownError)
|
|
39
|
+
if (exists === false) {
|
|
40
|
+
return yield* UnknownError.make({
|
|
41
|
+
cause: `Store module not found at ${abs}`,
|
|
42
|
+
note: 'Make sure the path points to a valid LiveStore module',
|
|
43
|
+
})
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const mod = yield* Effect.tryPromise({
|
|
47
|
+
try: () => import(pathToFileURL(abs).href),
|
|
48
|
+
catch: (cause) =>
|
|
49
|
+
UnknownError.make({
|
|
50
|
+
cause,
|
|
51
|
+
note: `Failed to import module at ${abs}`,
|
|
52
|
+
}),
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
const schema = (mod)?.schema
|
|
56
|
+
if (isLiveStoreSchema(schema) === false) {
|
|
57
|
+
return yield* UnknownError.make({
|
|
58
|
+
cause: `Module at ${abs} must export a valid LiveStore 'schema'`,
|
|
59
|
+
note: `Ex: export { schema } from './src/livestore/schema.ts'`,
|
|
60
|
+
})
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const syncBackendConstructor = (mod)?.syncBackend
|
|
64
|
+
if (typeof syncBackendConstructor !== 'function') {
|
|
65
|
+
return yield* UnknownError.make({
|
|
66
|
+
cause: `Module at ${abs} must export a 'syncBackend' constructor`,
|
|
67
|
+
note: `Ex: export const syncBackend = makeWsSync({ url })`,
|
|
68
|
+
})
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const syncPayloadSchemaExport = (mod)?.syncPayloadSchema
|
|
72
|
+
const syncPayloadSchema =
|
|
73
|
+
syncPayloadSchemaExport === undefined
|
|
74
|
+
? Schema.JsonValue
|
|
75
|
+
: Schema.isSchema(syncPayloadSchemaExport) === true
|
|
76
|
+
? (syncPayloadSchemaExport as Schema.Schema<any>)
|
|
77
|
+
: shouldNeverHappen(
|
|
78
|
+
`Exported 'syncPayloadSchema' from ${abs} must be an Effect Schema (received ${typeof syncPayloadSchemaExport}).`,
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
const syncPayloadExport = (mod)?.syncPayload
|
|
82
|
+
const syncPayload = yield* (
|
|
83
|
+
syncPayloadExport === undefined
|
|
84
|
+
? Effect.succeed<unknown>(undefined)
|
|
85
|
+
: Schema.decodeUnknown(syncPayloadSchema)(syncPayloadExport)
|
|
86
|
+
).pipe(UnknownError.mapToUnknownError)
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
schema,
|
|
90
|
+
syncBackendConstructor,
|
|
91
|
+
syncPayloadSchema,
|
|
92
|
+
syncPayload,
|
|
93
|
+
}
|
|
94
|
+
}).pipe(Effect.withSpan('module-loader:loadModuleConfig'))
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { Vitest } from '@livestore/utils-dev/node-vitest'
|
|
2
|
+
|
|
3
|
+
import { detectPackageManager, pmCommands } from './package-manager.ts'
|
|
4
|
+
|
|
5
|
+
Vitest.describe('detectPackageManager', () => {
|
|
6
|
+
Vitest.it('detects npm from user agent', () => {
|
|
7
|
+
const result = detectPackageManager('npm/10.2.4 node/v20.11.0 darwin arm64 workspaces/false')
|
|
8
|
+
Vitest.expect(result).toEqual({ _tag: 'supported', pm: 'npm' })
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
Vitest.it('detects pnpm from user agent', () => {
|
|
12
|
+
const result = detectPackageManager('pnpm/9.0.6 npm/? node/v22.0.0 darwin arm64')
|
|
13
|
+
Vitest.expect(result).toEqual({ _tag: 'supported', pm: 'pnpm' })
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
Vitest.it('detects yarn as unsupported', () => {
|
|
17
|
+
const result = detectPackageManager('yarn/1.22.19 npm/? node/v20.11.0 darwin arm64')
|
|
18
|
+
Vitest.expect(result).toEqual({ _tag: 'unsupported', pm: 'yarn' })
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
Vitest.it('detects bun from user agent', () => {
|
|
22
|
+
const result = detectPackageManager('bun/1.1.7')
|
|
23
|
+
Vitest.expect(result).toEqual({ _tag: 'supported', pm: 'bun' })
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
Vitest.it('falls back to bun for empty user agent', () => {
|
|
27
|
+
const result = detectPackageManager('')
|
|
28
|
+
Vitest.expect(result).toEqual({ _tag: 'supported', pm: 'bun' })
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
Vitest.it('uses process.env.npm_config_user_agent when no argument provided', () => {
|
|
32
|
+
const originalEnv = process.env.npm_config_user_agent
|
|
33
|
+
try {
|
|
34
|
+
process.env.npm_config_user_agent = 'pnpm/9.0.6'
|
|
35
|
+
const result = detectPackageManager()
|
|
36
|
+
Vitest.expect(result).toEqual({ _tag: 'supported', pm: 'pnpm' })
|
|
37
|
+
} finally {
|
|
38
|
+
process.env.npm_config_user_agent = originalEnv
|
|
39
|
+
}
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
Vitest.it('falls back to bun for unknown user agent', () => {
|
|
43
|
+
const result = detectPackageManager('some-unknown-tool/1.0.0')
|
|
44
|
+
Vitest.expect(result).toEqual({ _tag: 'supported', pm: 'bun' })
|
|
45
|
+
})
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
Vitest.describe('pmCommands', () => {
|
|
49
|
+
Vitest.it('provides correct install commands for each package manager', () => {
|
|
50
|
+
Vitest.expect(pmCommands.install.npm).toBe('npm install')
|
|
51
|
+
Vitest.expect(pmCommands.install.pnpm).toBe('pnpm install')
|
|
52
|
+
Vitest.expect(pmCommands.install.bun).toBe('bun install')
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
Vitest.it('provides correct run commands for dev script', () => {
|
|
56
|
+
Vitest.expect(pmCommands.run.npm('dev')).toBe('npm run dev')
|
|
57
|
+
Vitest.expect(pmCommands.run.pnpm('dev')).toBe('pnpm dev')
|
|
58
|
+
Vitest.expect(pmCommands.run.bun('dev')).toBe('bun dev')
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
Vitest.it('provides correct run commands for start script', () => {
|
|
62
|
+
Vitest.expect(pmCommands.run.npm('start')).toBe('npm run start')
|
|
63
|
+
Vitest.expect(pmCommands.run.pnpm('start')).toBe('pnpm start')
|
|
64
|
+
Vitest.expect(pmCommands.run.bun('start')).toBe('bun start')
|
|
65
|
+
})
|
|
66
|
+
})
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Supported package managers for LiveStore projects.
|
|
3
|
+
* Yarn is explicitly not supported due to compatibility issues.
|
|
4
|
+
*/
|
|
5
|
+
export type PackageManager = 'npm' | 'pnpm' | 'bun'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Result of package manager detection.
|
|
9
|
+
* Returns 'unsupported' for yarn to allow the CLI to show a warning message.
|
|
10
|
+
*/
|
|
11
|
+
export type DetectPackageManagerResult = { _tag: 'supported'; pm: PackageManager } | { _tag: 'unsupported'; pm: 'yarn' }
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Detects the package manager used to invoke the CLI based on the `npm_config_user_agent`
|
|
15
|
+
* environment variable. This env var is set by npm, pnpm, yarn, and bun when running scripts.
|
|
16
|
+
*
|
|
17
|
+
* - Returns 'unsupported' for yarn so the CLI can show a recommendation to use bun instead
|
|
18
|
+
* - Falls back to 'bun' when detection fails (e.g., when run directly without a package manager)
|
|
19
|
+
*/
|
|
20
|
+
export const detectPackageManager = (
|
|
21
|
+
userAgent = process.env.npm_config_user_agent ?? '',
|
|
22
|
+
): DetectPackageManagerResult => {
|
|
23
|
+
if (userAgent.startsWith('bun/') === true) return { _tag: 'supported', pm: 'bun' }
|
|
24
|
+
if (userAgent.startsWith('pnpm/') === true) return { _tag: 'supported', pm: 'pnpm' }
|
|
25
|
+
if (userAgent.startsWith('npm/') === true) return { _tag: 'supported', pm: 'npm' }
|
|
26
|
+
if (userAgent.startsWith('yarn/') === true) return { _tag: 'unsupported', pm: 'yarn' }
|
|
27
|
+
|
|
28
|
+
// Default to bun when the package manager can't be detected
|
|
29
|
+
return { _tag: 'supported', pm: 'bun' }
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Package manager command templates */
|
|
33
|
+
export const pmCommands = {
|
|
34
|
+
install: {
|
|
35
|
+
npm: 'npm install',
|
|
36
|
+
pnpm: 'pnpm install',
|
|
37
|
+
bun: 'bun install',
|
|
38
|
+
},
|
|
39
|
+
run: {
|
|
40
|
+
npm: (script: string) => `npm run ${script}`,
|
|
41
|
+
pnpm: (script: string) => `pnpm ${script}`,
|
|
42
|
+
bun: (script: string) => `bun ${script}`,
|
|
43
|
+
},
|
|
44
|
+
} as const
|