@livestore/cli 0.4.0-dev.18 → 0.4.0-dev.20

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@livestore/cli",
3
- "version": "0.4.0-dev.18",
3
+ "version": "0.4.0-dev.20",
4
4
  "type": "module",
5
5
  "sideEffects": false,
6
6
  "exports": {
@@ -11,16 +11,17 @@
11
11
  },
12
12
  "dependencies": {
13
13
  "@effect/ai-openai": "0.32.0",
14
- "@livestore/adapter-node": "0.4.0-dev.18",
15
- "@livestore/common": "0.4.0-dev.18",
16
- "@livestore/peer-deps": "0.4.0-dev.18",
17
- "@livestore/sync-cf": "0.4.0-dev.18",
18
- "@livestore/livestore": "0.4.0-dev.18",
19
- "@livestore/utils": "0.4.0-dev.18"
14
+ "@livestore/adapter-node": "0.4.0-dev.20",
15
+ "@livestore/common": "0.4.0-dev.20",
16
+ "@livestore/utils": "0.4.0-dev.20",
17
+ "@livestore/livestore": "0.4.0-dev.20",
18
+ "@livestore/peer-deps": "0.4.0-dev.20",
19
+ "@livestore/sync-cf": "0.4.0-dev.20"
20
20
  },
21
21
  "devDependencies": {
22
- "@types/node": "24.9.2",
23
- "typescript": "5.9.2"
22
+ "@types/node": "24.10.1",
23
+ "typescript": "5.9.2",
24
+ "@livestore/utils-dev": "0.4.0-dev.20"
24
25
  },
25
26
  "files": [
26
27
  "package.json",
@@ -1,5 +1,6 @@
1
1
  import * as os from 'node:os'
2
2
  import * as nodePath from 'node:path'
3
+ import { sluggify } from '@livestore/utils'
3
4
  import {
4
5
  Command,
5
6
  Console,
@@ -12,6 +13,8 @@ import {
12
13
  } from '@livestore/utils/effect'
13
14
  import { Cli } from '@livestore/utils/node'
14
15
 
16
+ import { detectPackageManager, pmCommands } from '../package-manager.ts'
17
+
15
18
  // Schema for GitHub API response
16
19
  const GitHubContentSchema = Schema.Struct({
17
20
  name: Schema.String,
@@ -22,6 +25,11 @@ const GitHubContentSchema = Schema.Struct({
22
25
 
23
26
  const GitHubContentsResponseSchema = Schema.Array(GitHubContentSchema)
24
27
 
28
+ /** Schema for parsing package.json scripts (dev or start) */
29
+ const PackageJsonScriptsSchema = Schema.Struct({
30
+ scripts: Schema.Union(Schema.Struct({ dev: Schema.String }), Schema.Struct({ start: Schema.String })),
31
+ })
32
+
25
33
  // Error types
26
34
  export class ExampleNotFoundError extends Schema.TaggedError<ExampleNotFoundError>()('ExampleNotFoundError', {
27
35
  exampleName: Schema.String,
@@ -40,11 +48,11 @@ export class DirectoryExistsError extends Schema.TaggedError<DirectoryExistsErro
40
48
  }) {}
41
49
 
42
50
  // Fetch available examples from GitHub
43
- const fetchExamples = (branch: string) =>
51
+ const fetchExamples = (ref: string) =>
44
52
  Effect.gen(function* () {
45
- const url = `https://api.github.com/repos/livestorejs/livestore/contents/examples?ref=${branch}`
53
+ const url = `https://api.github.com/repos/livestorejs/livestore/contents/examples?ref=${ref}`
46
54
 
47
- yield* Effect.log(`Fetching examples from branch: ${branch}`)
55
+ yield* Effect.log(`Fetching examples from ref: ${ref}`)
48
56
 
49
57
  const request = HttpClientRequest.get(url)
50
58
  const response = yield* HttpClient.execute(request).pipe(
@@ -100,13 +108,13 @@ const selectExample = (examples: string[]) =>
100
108
  })
101
109
 
102
110
  // Download and extract example using tiged approach
103
- const downloadExample = (exampleName: string, branch: string, destinationPath: string) =>
111
+ const downloadExample = (exampleName: string, ref: string, destinationPath: string) =>
104
112
  Effect.gen(function* () {
105
- yield* Console.log(`šŸ“„ Downloading example "${exampleName}" from branch "${branch}"...`)
113
+ yield* Console.log(`šŸ“„ Downloading example "${exampleName}" from ref "${ref}"...`)
106
114
 
107
115
  const tempDir = yield* Effect.sync(() => os.tmpdir())
108
- const tarballPath = nodePath.join(tempDir, `livestore-${branch}-${Date.now()}.tar.gz`)
109
- const tarballUrl = `https://api.github.com/repos/livestorejs/livestore/tarball/${branch}`
116
+ const tarballPath = nodePath.join(tempDir, `livestore-${sluggify(ref)}-${Date.now()}.tar.gz`)
117
+ const tarballUrl = `https://api.github.com/repos/livestorejs/livestore/tarball/${ref}`
110
118
 
111
119
  // Download tarball directly
112
120
  const request = HttpClientRequest.get(tarballUrl)
@@ -199,9 +207,14 @@ export const createCommand = Cli.Command.make(
199
207
  Cli.Options.optional,
200
208
  Cli.Options.withDescription('Example name to create (bypasses interactive selection)'),
201
209
  ),
202
- branch: Cli.Options.text('branch').pipe(
210
+ ref: Cli.Options.text('ref').pipe(
211
+ Cli.Options.withAlias('commit'),
212
+ Cli.Options.withAlias('branch'),
213
+ Cli.Options.withAlias('tag'),
203
214
  Cli.Options.withDefault('dev'),
204
- Cli.Options.withDescription('Branch to fetch examples from'),
215
+ Cli.Options.withDescription(
216
+ 'The name of the commit/branch/tag to fetch examples from. Pull requests refs must be fully-formed (e.g., `refs/pull/123/merge`).',
217
+ ),
205
218
  ),
206
219
  path: Cli.Args.text({ name: 'path' }).pipe(
207
220
  Cli.Args.optional,
@@ -210,17 +223,17 @@ export const createCommand = Cli.Command.make(
210
223
  },
211
224
  Effect.fn(function* ({
212
225
  example,
213
- branch,
226
+ ref,
214
227
  path,
215
228
  }: {
216
229
  example: Option.Option<string>
217
- branch: string
230
+ ref: string
218
231
  path: Option.Option<string>
219
232
  }) {
220
233
  yield* Effect.log('šŸš€ Creating new LiveStore project...')
221
234
 
222
235
  // Fetch available examples
223
- const examples = yield* fetchExamples(branch)
236
+ const examples = yield* fetchExamples(ref)
224
237
 
225
238
  if (examples.length === 0) {
226
239
  yield* Console.log('āŒ No examples found in the repository')
@@ -249,15 +262,45 @@ export const createCommand = Cli.Command.make(
249
262
  const destinationPath = Option.isSome(path) ? nodePath.resolve(path.value) : nodePath.resolve(selectedExample)
250
263
 
251
264
  // Download and extract the example
252
- yield* downloadExample(selectedExample, branch, destinationPath)
265
+ yield* downloadExample(selectedExample, ref, destinationPath)
266
+
267
+ // Detect available run script (dev or start) from the created project's package.json.
268
+ // Some examples use "dev" (web projects), others use "start" (Expo projects),
269
+ // and some have no run script at all (e.g., node-effect-cli).
270
+ const fs = yield* FileSystem.FileSystem
271
+ const packageJsonPath = nodePath.join(destinationPath, 'package.json')
272
+ const packageJsonContent = yield* fs.readFileString(packageJsonPath)
273
+ const runScript = yield* Schema.decodeUnknown(Schema.parseJson(PackageJsonScriptsSchema))(packageJsonContent).pipe(
274
+ Effect.map((pkg) => ('dev' in pkg.scripts ? ('dev' as const) : ('start' as const))),
275
+ Effect.orElseSucceed(() => undefined),
276
+ )
277
+
278
+ // Detect which package manager was used to invoke the CLI (via npm_config_user_agent).
279
+ // This ensures the "next steps" instructions match how the user ran the create command.
280
+ const pmResult = detectPackageManager()
253
281
 
254
- // Success message
255
282
  yield* Console.log('\nšŸŽ‰ Project created successfully!')
256
283
  yield* Console.log(`šŸ“ Location: ${destinationPath}`)
257
284
  yield* Console.log('\nšŸ“‹ Next steps:')
258
285
  yield* Console.log(` cd ${nodePath.basename(destinationPath)}`)
259
- yield* Console.log(' pnpm install # Install dependencies')
260
- yield* Console.log(' pnpm dev # Start development server')
286
+
287
+ // Yarn is not recommended for LiveStore projects. When detected, show a warning
288
+ // and suggest using bun instead for the next steps.
289
+ if (pmResult._tag === 'unsupported') {
290
+ yield* Console.log(' bun install # Install dependencies (yarn is not recommended)')
291
+ if (runScript !== undefined) {
292
+ yield* Console.log(` bun ${runScript} # Start development server`)
293
+ }
294
+ yield* Console.log('\nāš ļø Yarn is not recommended for LiveStore projects.')
295
+ yield* Console.log(' We recommend using bun, pnpm, or npm instead.')
296
+ yield* Console.log(' The commands above use bun by default.')
297
+ } else {
298
+ const pm = pmResult.pm
299
+ yield* Console.log(` ${pmCommands.install[pm]} # Install dependencies`)
300
+ if (runScript !== undefined) {
301
+ yield* Console.log(` ${pmCommands.run[pm](runScript)} # Start development server`)
302
+ }
303
+ }
261
304
  yield* Console.log('\nšŸ’” Tip: Run `git init` if you want to initialize version control')
262
305
  }),
263
306
  )
@@ -155,8 +155,8 @@ export const commit = ({ events }: { events: ReadonlyArray<{ name: string; args:
155
155
  return yield* Effect.dieMessage('LiveStore not connected. Call livestore_instance_connect first.')
156
156
  }
157
157
  const s = opt.value
158
- const PartialEventSchema = LiveStoreEvent.makeEventDefPartialSchema(s.schema) as Schema.Schema<any>
159
- const decoded = events.map((e) => Schema.decodeSync(PartialEventSchema)(e))
158
+ const InputEventSchema = LiveStoreEvent.Input.makeSchema(s.schema) as Schema.Schema<any>
159
+ const decoded = events.map((e) => Schema.decodeSync(InputEventSchema)(e))
160
160
  s.commit(...decoded)
161
161
  return { committed: decoded.length }
162
162
  }).pipe(Effect.withSpan('mcp-runtime:commit'))
@@ -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/')) return { _tag: 'supported', pm: 'bun' }
24
+ if (userAgent.startsWith('pnpm/')) return { _tag: 'supported', pm: 'pnpm' }
25
+ if (userAgent.startsWith('npm/')) return { _tag: 'supported', pm: 'npm' }
26
+ if (userAgent.startsWith('yarn/')) 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