@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/dist/cli.d.ts +1 -1
- package/dist/commands/new-project.d.ts +1 -1
- package/dist/commands/new-project.d.ts.map +1 -1
- package/dist/commands/new-project.js +45 -14
- package/dist/commands/new-project.js.map +1 -1
- package/dist/mcp-runtime/runtime.js +2 -2
- package/dist/mcp-runtime/runtime.js.map +1 -1
- 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/tsconfig.tsbuildinfo +1 -1
- package/package.json +10 -9
- package/src/commands/new-project.ts +59 -16
- package/src/mcp-runtime/runtime.ts +2 -2
- package/src/package-manager.test.ts +66 -0
- package/src/package-manager.ts +44 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@livestore/cli",
|
|
3
|
-
"version": "0.4.0-dev.
|
|
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.
|
|
15
|
-
"@livestore/common": "0.4.0-dev.
|
|
16
|
-
"@livestore/
|
|
17
|
-
"@livestore/
|
|
18
|
-
"@livestore/
|
|
19
|
-
"@livestore/
|
|
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.
|
|
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 = (
|
|
51
|
+
const fetchExamples = (ref: string) =>
|
|
44
52
|
Effect.gen(function* () {
|
|
45
|
-
const url = `https://api.github.com/repos/livestorejs/livestore/contents/examples?ref=${
|
|
53
|
+
const url = `https://api.github.com/repos/livestorejs/livestore/contents/examples?ref=${ref}`
|
|
46
54
|
|
|
47
|
-
yield* Effect.log(`Fetching examples from
|
|
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,
|
|
111
|
+
const downloadExample = (exampleName: string, ref: string, destinationPath: string) =>
|
|
104
112
|
Effect.gen(function* () {
|
|
105
|
-
yield* Console.log(`š„ Downloading example "${exampleName}" from
|
|
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-${
|
|
109
|
-
const tarballUrl = `https://api.github.com/repos/livestorejs/livestore/tarball/${
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
226
|
+
ref,
|
|
214
227
|
path,
|
|
215
228
|
}: {
|
|
216
229
|
example: Option.Option<string>
|
|
217
|
-
|
|
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(
|
|
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,
|
|
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
|
-
|
|
260
|
-
|
|
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
|
|
159
|
-
const decoded = events.map((e) => Schema.decodeSync(
|
|
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
|