create-jst 0.3.0 → 0.3.1

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/README.md CHANGED
@@ -15,5 +15,3 @@ npm create jst@latest my-app -- --dry-run
15
15
  ```
16
16
 
17
17
  Use `--help` to view non-interactive options. The initializer never overwrites a non-empty directory.
18
-
19
- See [Architecture](./ARCHITECTURE.md) for the dependency boundaries and replaceable gateways.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-jst",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "Create a web application from the JST template.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -25,6 +25,7 @@
25
25
  "homepage": "https://github.com/jst-stack/jst#readme",
26
26
  "license": "MIT",
27
27
  "dependencies": {
28
+ "@clack/prompts": "^1.8.1",
28
29
  "degit": "^3.10.0"
29
30
  }
30
31
  }
package/src/cli.mjs CHANGED
@@ -17,9 +17,9 @@ export async function run(argv, environment = process, application = createProje
17
17
  if (command.values.version) return environment.stdout.write(`${process.env.npm_package_version ?? '0.3.0'}\n`)
18
18
 
19
19
  const request = await resolveProjectRequest(command, environment)
20
- if (request.dryRun) return reporter.plan(request.specification)
20
+ if (request.dryRun) return reporter.plan(request.specification, request)
21
21
 
22
- reporter.start()
22
+ reporter.start(request)
23
23
  const unsubscribe = reporter.subscribe(application.events)
24
24
  try {
25
25
  await application.initializer.execute(request.specification)
@@ -29,6 +29,7 @@ export async function run(argv, environment = process, application = createProje
29
29
  }
30
30
  }
31
31
  catch (error) {
32
+ if (error instanceof Error && error.name === 'PromptCancelledError') return
32
33
  reporter.error(error instanceof Error ? error.message : String(error))
33
34
  environment.exitCode = 1
34
35
  }
@@ -62,21 +62,25 @@ export function createNodeProjectGateway({ nodeExecutable }) {
62
62
  }
63
63
 
64
64
  function installDependencies(destination, packageManager) {
65
- return run(packageManager, installCommands[packageManager], destination, true)
65
+ return run(packageManager, installCommands[packageManager], destination)
66
66
  }
67
67
  }
68
68
 
69
- function run(command, args, cwd, inheritOutput = false) {
69
+ function run(command, args, cwd) {
70
70
  return new Promise((resolve, reject) => {
71
- const child = spawn(command, args, { cwd, stdio: inheritOutput ? 'inherit' : 'pipe' })
71
+ const child = spawn(command, args, { cwd, stdio: 'pipe' })
72
+ let stdout = ''
72
73
  let stderr = ''
74
+ child.stdout?.on('data', chunk => {
75
+ stdout += chunk
76
+ })
73
77
  child.stderr?.on('data', chunk => {
74
78
  stderr += chunk
75
79
  })
76
80
  child.once('error', error => reject(new Error(`Could not run ${command}: ${error.message}`)))
77
81
  child.once('close', code => {
78
82
  if (code === 0) resolve()
79
- else reject(new Error(`${command} ${args.join(' ')} failed.${stderr ? `\n${stderr.trim()}` : ''}`))
83
+ else reject(new Error(`${command} ${args.join(' ')} failed.${[stdout, stderr].filter(Boolean).join('\n').trim() ? `\n${[stdout, stderr].filter(Boolean).join('\n').trim()}` : ''}`))
80
84
  })
81
85
  })
82
86
  }
@@ -1,5 +1,6 @@
1
1
  import { basename, resolve } from 'node:path'
2
2
  import { parseArgs } from 'node:util'
3
+ import * as prompts from '@clack/prompts'
3
4
  import { buildProjectSpecification, normalizePackageName, PACKAGE_MANAGERS, toTitle } from '../domain/projectSpecification.mjs'
4
5
 
5
6
  export function parseCommandLine(args) {
@@ -51,13 +52,15 @@ export async function resolveProjectRequest(command, environment) {
51
52
 
52
53
  return {
53
54
  dryRun: values['dry-run'] ?? false,
55
+ interactive: Boolean(prompt),
56
+ terminal: Boolean(environment.stdin.isTTY),
54
57
  specification: buildProjectSpecification({
55
58
  colorScheme: values['color-scheme'],
56
59
  demo: values.demo ?? await prompt?.choice('Start from', ['remove', 'keep'], 'remove') ?? 'remove',
57
60
  description: values.description ?? `${title} web application.`,
58
61
  destination: resolve(environment.cwd(), directory),
59
- initGit: prompt ? await prompt.choice('Initialize a Git repository', ['yes', 'no'], initGit ? 'yes' : 'no') === 'yes' : initGit,
60
- install: prompt ? await prompt.choice('Install dependencies', ['yes', 'no'], install ? 'yes' : 'no') === 'yes' : install,
62
+ initGit: prompt ? await prompt.confirm('Initialize a Git repository?', initGit) : initGit,
63
+ install: prompt ? await prompt.confirm('Install dependencies?', install) : install,
61
64
  language: values.lang,
62
65
  name,
63
66
  packageManager,
@@ -110,21 +113,45 @@ Options:
110
113
 
111
114
  async function createPrompt(environment, values) {
112
115
  if (!environment.stdin.isTTY || values.yes || values['no-interactive']) return undefined
113
- const { createInterface } = await import('node:readline/promises')
114
- const reader = createInterface({ input: environment.stdin, output: environment.stdout })
116
+ prompts.intro('create-jst')
115
117
  return {
116
118
  async choice(label, choices, fallback) {
117
- const rendered = choices.map((choice, index) => `${index + 1}:${choice}`).join(' ')
118
- const answer = (await reader.question(` ${label} (${rendered}) [${fallback}]: `)).trim()
119
- if (!answer) return fallback
120
- const index = Number.parseInt(answer, 10)
121
- return choices[index - 1] ?? answer
119
+ return resolvePrompt(await prompts.select({
120
+ initialValue: fallback,
121
+ message: label,
122
+ options: choices.map(value => ({ hint: choiceHint(value), label: choiceLabel(value), value })),
123
+ }))
122
124
  },
123
125
  close() {
124
- reader.close()
126
+ return undefined
127
+ },
128
+ async confirm(label, initialValue) {
129
+ return resolvePrompt(await prompts.confirm({ initialValue, message: label }))
125
130
  },
126
131
  async text(label, fallback) {
127
- return (await reader.question(` ${label} [${fallback}]: `)).trim() || fallback
132
+ return resolvePrompt(await prompts.text({ initialValue: fallback, message: label }))
128
133
  },
129
134
  }
130
135
  }
136
+
137
+ function choiceLabel(value) {
138
+ return {
139
+ keep: 'Keep the JST demo',
140
+ remove: 'Start with a clean application',
141
+ }[value] ?? value
142
+ }
143
+
144
+ function choiceHint(value) {
145
+ return {
146
+ npm: 'default',
147
+ remove: 'recommended',
148
+ }[value]
149
+ }
150
+
151
+ function resolvePrompt(value) {
152
+ if (!prompts.isCancel(value)) return value
153
+ prompts.cancel('Setup cancelled.')
154
+ const error = new Error('Setup cancelled.')
155
+ error.name = 'PromptCancelledError'
156
+ throw error
157
+ }
@@ -1,4 +1,5 @@
1
1
  import { basename } from 'node:path'
2
+ import * as prompts from '@clack/prompts'
2
3
 
3
4
  const progressMessages = {
4
5
  'dependencies.installation.started': specification => `Installing dependencies with ${specification.packageManager}`,
@@ -12,12 +13,32 @@ export function createReporter(output, environment = {}) {
12
13
  const color = Boolean(output.isTTY) && !environment.NO_COLOR
13
14
  const paint = (code, value) => color ? `\u001B[${code}m${value}\u001B[0m` : value
14
15
  const dim = value => paint('2', value)
16
+ let activeSpinner
17
+ let usesClack = false
15
18
 
16
19
  return {
17
20
  error(message) {
21
+ if (usesClack) {
22
+ activeSpinner?.stop('Failed')
23
+ prompts.cancel(message)
24
+ return
25
+ }
18
26
  output.write(`\n${paint('31', '✕')} ${message}\n`)
19
27
  },
20
- plan(specification) {
28
+ plan(specification, request) {
29
+ usesClack = request.terminal
30
+ if (usesClack) {
31
+ if (!request.interactive) prompts.intro('create-jst')
32
+ prompts.note([
33
+ `Template: ${specification.template}`,
34
+ `Package manager: ${specification.packageManager}`,
35
+ `Starter demo: ${specification.demo}`,
36
+ `Install dependencies: ${specification.install ? 'yes' : 'no'}`,
37
+ `Initialize Git: ${specification.initGit ? 'yes' : 'no'}`,
38
+ ].join('\n'), specification.destination)
39
+ prompts.outro('Dry run complete — no files were written.')
40
+ return
41
+ }
21
42
  output.write(`\n${paint('38;5;203', 'JST')} ${dim('plan only — no files will be written')}\n\n`)
22
43
  output.write(` destination ${specification.destination}\n`)
23
44
  output.write(` template ${specification.template}\n`)
@@ -26,17 +47,34 @@ export function createReporter(output, environment = {}) {
26
47
  output.write(` install ${specification.install ? 'yes' : 'no'}\n`)
27
48
  output.write(` git ${specification.initGit ? 'yes' : 'no'}\n\n`)
28
49
  },
29
- start() {
50
+ start(request) {
51
+ usesClack = request.terminal
52
+ if (usesClack) {
53
+ if (!request.interactive) prompts.intro('create-jst')
54
+ return
55
+ }
30
56
  output.write(`\n${paint('38;5;203', 'JST')} ${dim('A foundation for replaceable layers')}\n\n`)
31
57
  },
32
58
  subscribe(events) {
33
59
  return events.subscribe(event => {
34
60
  if (event.type === 'project.initialized') return this.success(event.specification)
35
61
  const message = progressMessages[event.type]
36
- if (message) output.write(`${dim(' ◌')} ${message(event.specification)}\n`)
62
+ if (!message) return
63
+ if (usesClack) {
64
+ activeSpinner?.stop()
65
+ activeSpinner = prompts.spinner()
66
+ activeSpinner.start(message(event.specification))
67
+ return
68
+ }
69
+ output.write(`${dim(' ◌')} ${message(event.specification)}\n`)
37
70
  })
38
71
  },
39
72
  success(specification) {
73
+ if (usesClack) {
74
+ activeSpinner?.stop('Project created')
75
+ prompts.outro(`Ready. Run \`${specification.packageManager} run dev\` in ${basename(specification.destination)}.`)
76
+ return
77
+ }
40
78
  output.write(`\n${paint('38;5;114', 'Ready.')} ${specification.title} is in ${paint('38;5;203', basename(specification.destination))}.\n\n`)
41
79
  output.write(` ${dim('$')} cd ${basename(specification.destination)}\n`)
42
80
  output.write(` ${dim('$')} ${specification.packageManager} run dev\n\n`)