create-jst 0.4.27 → 0.4.29

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": "create-jst",
3
- "version": "0.4.27",
3
+ "version": "0.4.29",
4
4
  "description": "Create a web application from the JST template.",
5
5
  "type": "module",
6
6
  "packageManager": "npm@11.6.2",
@@ -14,11 +14,12 @@
14
14
  "engines": {
15
15
  "node": ">=22.14.0"
16
16
  },
17
- "scripts": {
17
+ "scripts": {
18
+ "audit:sources": "node scripts/audit-sources.mjs",
18
19
  "lint": "eslint . --max-warnings 0",
19
20
  "benchmark": "node scripts/run-benchmarks.mjs",
20
21
  "benchmark:update": "node scripts/run-benchmarks.mjs --write-baseline",
21
- "test": "node --test test/integration.test.mjs test/migration.test.mjs test/options.test.mjs test/projectInitializer.test.mjs",
22
+ "test": "node --test test/integration.test.mjs test/migration.test.mjs test/nodeProjectGateway.test.mjs test/options.test.mjs test/projectInitializer.test.mjs",
22
23
  "test:generated": "node --test test/generatedProject.test.mjs",
23
24
  "check": "npm run lint && npm test && npm pack --dry-run",
24
25
  "prepublishOnly": "npm run check"
@@ -48,6 +49,7 @@
48
49
  },
49
50
  "devDependencies": {
50
51
  "@eslint/js": "^10.0.1",
52
+ "@jst-stack/eslint-plugin-quality": "^0.2.1",
51
53
  "eslint": "^10.10.0"
52
54
  }
53
55
  }
@@ -7,7 +7,7 @@ export function createProjectInitializer({ onProgress = () => undefined, project
7
7
  let rollbackDestination = temporaryDestination
8
8
 
9
9
  try {
10
- onProgress({ type: 'template.download.started', specification })
10
+ onProgress({ type: 'template.download.started', specification })
11
11
  await templateGateway.clone(specification.template, temporaryDestination)
12
12
  if (specification.example === 'clean') {
13
13
  await projectGateway.validateTemplate(temporarySpecification)
@@ -16,8 +16,7 @@ export function createProjectInitializer({ onProgress = () => undefined, project
16
16
  onProgress({ type: 'project.configuration.started', specification })
17
17
  if (specification.example === 'showcase') {
18
18
  await projectGateway.configureExample(temporarySpecification)
19
- }
20
- else {
19
+ } else {
21
20
  await projectGateway.configureTemplate(temporarySpecification)
22
21
  }
23
22
 
@@ -28,8 +27,7 @@ export function createProjectInitializer({ onProgress = () => undefined, project
28
27
  if (specification.initGit) {
29
28
  onProgress({ type: 'git.initialization.started', specification })
30
29
  await projectGateway.initializeGit(temporaryDestination)
31
- }
32
- else {
30
+ } else {
33
31
  await projectGateway.removeGitMetadata(temporaryDestination)
34
32
  }
35
33
  if (specification.install) {
@@ -39,16 +37,13 @@ export function createProjectInitializer({ onProgress = () => undefined, project
39
37
  rollbackDestination = specification.destination
40
38
  if (specification.install) {
41
39
  onProgress({ type: 'dependencies.installation.started', specification })
42
- await projectGateway.installDependencies(
43
- specification.destination,
44
- specification.packageManager,
45
- { gitHooks: specification.initGit },
46
- )
40
+ await projectGateway.installDependencies(specification.destination, specification.packageManager, {
41
+ gitHooks: specification.initGit,
42
+ })
47
43
  }
48
44
 
49
45
  onProgress({ type: 'project.initialized', specification })
50
- }
51
- catch (error) {
46
+ } catch (error) {
52
47
  await projectGateway.cleanupDestination(rollbackDestination)
53
48
  throw error
54
49
  }
package/src/cli.mjs CHANGED
@@ -30,17 +30,16 @@ export async function run(argv, environment = process, injectedApplication) {
30
30
  }
31
31
 
32
32
  reporter.start(request)
33
- const application = injectedApplication ?? createProjectApplication({ onProgress: event => reporter.progress(event), signal: signals.signal })
33
+ const application =
34
+ injectedApplication ?? createProjectApplication({ onProgress: (event) => reporter.progress(event), signal: signals.signal })
34
35
  await application.initializer.execute(request.specification)
35
- }
36
- catch (error) {
36
+ } catch (error) {
37
37
  if (error instanceof Error && error.name === 'PromptCancelledError') {
38
38
  return
39
39
  }
40
40
  reporter.error(error instanceof Error ? error.message : String(error))
41
41
  environment.exitCode = 1
42
- }
43
- finally {
42
+ } finally {
44
43
  signals.dispose()
45
44
  }
46
45
  }
@@ -8,13 +8,18 @@ export const migrations = Object.freeze([
8
8
  apply(files) {
9
9
  const eslintConfig = files.get('eslint.config.js')
10
10
  if (!eslintConfig?.includes("jst.configs['strict']")) {
11
- throw new Error("Migration requires eslint.config.js to use jst.configs['strict']; migrate custom ESLint composition manually.")
11
+ throw new Error(
12
+ "Migration requires eslint.config.js to use jst.configs['strict']; migrate custom ESLint composition manually.",
13
+ )
12
14
  }
13
15
  const withPolicy = eslintConfig.includes("from './jst.config.ts'")
14
16
  ? eslintConfig
15
17
  : `import policy from './jst.config.ts'\n${eslintConfig}`
16
18
  files.set('eslint.config.js', withPolicy.replace("jst.configs['strict']", 'jst.createConfig(policy)'))
17
- files.set('jst.config.ts', "import { defineConfig } from '@jst-stack/eslint-plugin/policy'\n\nexport default defineConfig({})\n")
19
+ files.set(
20
+ 'jst.config.ts',
21
+ "import { defineConfig } from '@jst-stack/eslint-plugin/policy'\n\nexport default defineConfig({})\n",
22
+ )
18
23
  },
19
24
  }),
20
25
  ])
@@ -23,7 +28,7 @@ export function resolveMigrationPath(from, to = CURRENT_PROJECT_VERSION) {
23
28
  const path = []
24
29
  let version = from
25
30
  while (version !== to) {
26
- const migration = migrations.find(candidate => candidate.from === version)
31
+ const migration = migrations.find((candidate) => candidate.from === version)
27
32
  if (!migration || path.includes(migration)) {
28
33
  throw new Error(`No supported migration path from ${from} to ${to}.`)
29
34
  }
@@ -1,10 +1,25 @@
1
1
  export const COLOR_SCHEMES = ['light', 'dark', 'auto']
2
- export const PRIMARY_COLORS = ['dark', 'gray', 'red', 'pink', 'grape', 'violet', 'indigo', 'blue', 'cyan', 'teal', 'green', 'lime', 'yellow', 'orange']
2
+ export const PRIMARY_COLORS = [
3
+ 'dark',
4
+ 'gray',
5
+ 'red',
6
+ 'pink',
7
+ 'grape',
8
+ 'violet',
9
+ 'indigo',
10
+ 'blue',
11
+ 'cyan',
12
+ 'teal',
13
+ 'green',
14
+ 'lime',
15
+ 'yellow',
16
+ 'orange',
17
+ ]
3
18
  export const PACKAGE_MANAGERS = ['npm', 'pnpm']
4
19
  export const STYLE_LANGUAGES = ['css', 'scss']
5
20
  export const EXAMPLES = ['clean', 'showcase']
6
- export const TEMPLATE_SOURCE = 'jst-stack/jst#v0.4.26'
7
- export const SHOWCASE_SOURCE = 'jst-stack/jst-showcase#33239c1447f9cd82c3a148ebd0216aa3225eeceb'
21
+ export const TEMPLATE_SOURCE = 'jst-stack/jst#v0.4.29'
22
+ export const SHOWCASE_SOURCE = 'jst-stack/jst-showcase#da0fbe07b2a2d529dce7a0178ea756caa87238f4'
8
23
 
9
24
  export function buildProjectSpecification(input) {
10
25
  const specification = Object.freeze({
@@ -54,9 +69,19 @@ export function validateProjectSpecification(specification) {
54
69
  }
55
70
 
56
71
  export function normalizePackageName(value) {
57
- return value.toLowerCase().replace(/[^a-z0-9._-]+/g, '-').replace(/^[^a-z0-9]+|[^a-z0-9]+$/g, '') || 'jst-app'
72
+ return (
73
+ value
74
+ .toLowerCase()
75
+ .replace(/[^a-z0-9._-]+/g, '-')
76
+ .replace(/^[^a-z0-9]+|[^a-z0-9]+$/g, '') || 'jst-app'
77
+ )
58
78
  }
59
79
 
60
80
  export function toTitle(value) {
61
- return value.replace(/^@[^/]+\//, '').split(/[-_.]+/).filter(Boolean).map(word => word[0].toUpperCase() + word.slice(1)).join(' ')
81
+ return value
82
+ .replace(/^@[^/]+\//, '')
83
+ .split(/[-_.]+/)
84
+ .filter(Boolean)
85
+ .map((word) => word[0].toUpperCase() + word.slice(1))
86
+ .join(' ')
62
87
  }
@@ -5,17 +5,17 @@ import process from 'node:process'
5
5
  import { promisify } from 'node:util'
6
6
 
7
7
  const executeFile = promisify(execFile)
8
+ const commandTimeout = 600_000
8
9
 
9
10
  export function createNodeMigrationGateway() {
10
11
  return {
11
12
  async assertSafeGitState(directory) {
12
13
  try {
13
- const { stdout } = await executeFile('git', ['status', '--porcelain'], { cwd: directory })
14
+ const { stdout } = await executeFile('git', ['status', '--porcelain'], { cwd: directory, timeout: commandTimeout })
14
15
  if (stdout.trim()) {
15
16
  throw new Error('Migration requires a clean Git working tree.')
16
17
  }
17
- }
18
- catch (error) {
18
+ } catch (error) {
19
19
  if (error.message === 'Migration requires a clean Git working tree.') {
20
20
  throw error
21
21
  }
@@ -23,7 +23,7 @@ export function createNodeMigrationGateway() {
23
23
  }
24
24
  },
25
25
  async readMigrationFiles(directory, migrations) {
26
- const names = new Set(migrations.flatMap(migration => migration.files))
26
+ const names = new Set(migrations.flatMap((migration) => migration.files))
27
27
  const files = new Map()
28
28
  for (const name of names) {
29
29
  files.set(name, await readFile(join(directory, name), 'utf8'))
@@ -36,6 +36,7 @@ export function createNodeMigrationGateway() {
36
36
  return executeFile(packageManager, ['run', 'check'], {
37
37
  cwd: directory,
38
38
  shell: process.platform === 'win32',
39
+ timeout: commandTimeout,
39
40
  })
40
41
  },
41
42
  async writeTransaction(directory, files, verify) {
@@ -43,8 +44,7 @@ export function createNodeMigrationGateway() {
43
44
  for (const name of files.keys()) {
44
45
  try {
45
46
  originals.set(name, await readFile(join(directory, name), 'utf8'))
46
- }
47
- catch (error) {
47
+ } catch (error) {
48
48
  if (error.code !== 'ENOENT') {
49
49
  throw error
50
50
  }
@@ -55,13 +55,11 @@ export function createNodeMigrationGateway() {
55
55
  await writeFile(join(directory, name), content)
56
56
  }
57
57
  await verify()
58
- }
59
- catch (error) {
58
+ } catch (error) {
60
59
  for (const name of files.keys()) {
61
60
  if (originals.has(name)) {
62
61
  await writeFile(join(directory, name), originals.get(name))
63
- }
64
- else {
62
+ } else {
65
63
  await rm(join(directory, name), { force: true })
66
64
  }
67
65
  }
@@ -12,15 +12,16 @@ const installCommands = {
12
12
  }
13
13
  const pnpmAction = 'pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0'
14
14
  const textExtensions = new Set(['.md', '.mjs', '.yaml', '.yml'])
15
+ const commandTimeout = 600_000
15
16
 
16
- export function createNodeProjectGateway({ nodeExecutable, signal }) {
17
+ export function createNodeProjectGateway({ nodeExecutable, signal, timeout = commandTimeout }) {
17
18
  return {
18
19
  assertDestinationIsEmpty,
19
20
  assertRuntimeSupported,
20
21
  cleanupDestination,
21
22
  commitDestination,
22
23
  configureExample,
23
- configurePackageManager: (destination, packageManager) => configurePackageManager(destination, packageManager, signal),
24
+ configurePackageManager: (destination, packageManager) => configurePackageManager(destination, packageManager, signal, timeout),
24
25
  configureTemplate,
25
26
  initializeGit,
26
27
  installDependencies,
@@ -52,22 +53,34 @@ export function createNodeProjectGateway({ nodeExecutable, signal }) {
52
53
  }
53
54
 
54
55
  function configureTemplate(specification) {
55
- return run(nodeExecutable, [
56
- 'scripts/setup-template.mjs',
57
- '--yes',
58
- '--quiet',
59
- '--name', specification.name,
60
- '--title', specification.title,
61
- '--description', specification.description,
62
- '--lang', specification.language,
63
- '--color-scheme', specification.colorScheme,
64
- '--primary-color', specification.primaryColor,
65
- '--style', specification.style,
66
- ], specification.destination, { signal })
56
+ return run(
57
+ nodeExecutable,
58
+ [
59
+ 'scripts/setup-template.mjs',
60
+ '--yes',
61
+ '--quiet',
62
+ '--name',
63
+ specification.name,
64
+ '--title',
65
+ specification.title,
66
+ '--description',
67
+ specification.description,
68
+ '--lang',
69
+ specification.language,
70
+ '--color-scheme',
71
+ specification.colorScheme,
72
+ '--primary-color',
73
+ specification.primaryColor,
74
+ '--style',
75
+ specification.style,
76
+ ],
77
+ specification.destination,
78
+ { signal, timeout },
79
+ )
67
80
  }
68
81
 
69
82
  function initializeGit(destination) {
70
- return run('git', ['init', '--initial-branch=main'], destination, { signal })
83
+ return run('git', ['init', '--initial-branch=main'], destination, { signal, timeout })
71
84
  }
72
85
 
73
86
  function installDependencies(destination, packageManager, { gitHooks = true } = {}) {
@@ -75,6 +88,7 @@ export function createNodeProjectGateway({ nodeExecutable, signal }) {
75
88
  env: gitHooks ? undefined : { ...process.env, HUSKY: '0' },
76
89
  shell: process.platform === 'win32',
77
90
  signal,
91
+ timeout,
78
92
  })
79
93
  }
80
94
 
@@ -132,7 +146,7 @@ function compareVersions(left, right) {
132
146
  return 0
133
147
  }
134
148
 
135
- async function configurePackageManager(destination, packageManager, signal) {
149
+ async function configurePackageManager(destination, packageManager, signal, timeout) {
136
150
  const manifestPath = join(destination, 'package.json')
137
151
  const manifest = JSON.parse(await readFile(manifestPath, 'utf8'))
138
152
  delete manifest.packageManager
@@ -141,20 +155,20 @@ async function configurePackageManager(destination, packageManager, signal) {
141
155
 
142
156
  let version
143
157
  try {
144
- ({ stdout: version } = await executeFile(packageManager, ['--version'], {
158
+ ;({ stdout: version } = await executeFile(packageManager, ['--version'], {
145
159
  cwd: destination,
146
160
  shell: process.platform === 'win32',
147
161
  signal,
162
+ timeout,
148
163
  }))
149
- }
150
- catch {
164
+ } catch {
151
165
  throw new Error(`Could not run ${packageManager}. Install it or choose another package manager.`)
152
166
  }
153
167
  if (packageManager === 'pnpm') {
154
168
  manifest.scripts = replaceManifestCommands(manifest.scripts)
155
169
  }
156
170
  const packageManagerEntry = ['packageManager', `${packageManager}@${version.trim()}`]
157
- const entries = Object.entries(manifest).flatMap(entry => entry[0] === 'engines' ? [packageManagerEntry, entry] : [entry])
171
+ const entries = Object.entries(manifest).flatMap((entry) => (entry[0] === 'engines' ? [packageManagerEntry, entry] : [entry]))
158
172
  if (!manifest.engines) {
159
173
  entries.push(packageManagerEntry)
160
174
  }
@@ -190,8 +204,7 @@ async function replaceProjectCommands(directory) {
190
204
  const path = join(directory, entry.name)
191
205
  if (entry.isDirectory()) {
192
206
  await replaceProjectCommands(path)
193
- }
194
- else if (textExtensions.has(extname(entry.name)) || entry.name === 'Dockerfile') {
207
+ } else if (textExtensions.has(extname(entry.name)) || entry.name === 'Dockerfile') {
195
208
  const source = await readFile(path, 'utf8')
196
209
  const converted = entry.name === 'Dockerfile' ? replacePnpmDockerfile(source) : replacePnpmText(source)
197
210
  if (converted !== source) {
@@ -230,15 +243,19 @@ async function prepareDestination(destination) {
230
243
  return temporaryDestination
231
244
  }
232
245
 
233
- function run(command, args, cwd, { env, shell = false, signal } = {}) {
246
+ function run(command, args, cwd, { env, shell = false, signal, timeout } = {}) {
234
247
  return new Promise((resolve, reject) => {
235
- const child = spawn(command, args, { cwd, env, shell, signal, stdio: 'inherit' })
236
- child.once('error', error => reject(new Error(`Could not run ${command}: ${error.message}`)))
237
- child.once('close', code => {
248
+ const child = spawn(command, args, { cwd, env, shell, signal, stdio: 'inherit', timeout })
249
+ child.once('error', (error) => reject(new Error(`Could not run ${command}: ${error.message}`)))
250
+ child.once('close', (code, terminationSignal) => {
238
251
  if (code === 0) {
239
252
  resolve()
240
253
  return
241
254
  }
255
+ if (terminationSignal && !signal?.aborted) {
256
+ reject(new Error(`${command} timed out after ${timeout}ms.`))
257
+ return
258
+ }
242
259
  reject(new Error(`${command} ${args.join(' ')} failed with exit code ${code}.`))
243
260
  })
244
261
  })
package/src/jst.mjs CHANGED
@@ -15,19 +15,34 @@ export async function run(argv, environment = process, migrator = createProjectM
15
15
  return
16
16
  }
17
17
  const migrations = resolveMigrationPath(command.from, command.to)
18
- const result = await migrator.execute({ directory: environment.cwd(), dryRun: command.dryRun, from: command.from, migrations, to: command.to ?? migrations.at(-1)?.to ?? command.from })
19
- environment.stdout.write(`${command.dryRun ? 'Would migrate' : 'Migrated'} ${result.from} → ${result.to}: ${result.changedFiles.join(', ')}\n`)
20
- }
21
- catch (error) {
18
+ const result = await migrator.execute({
19
+ directory: environment.cwd(),
20
+ dryRun: command.dryRun,
21
+ from: command.from,
22
+ migrations,
23
+ to: command.to ?? migrations.at(-1)?.to ?? command.from,
24
+ })
25
+ environment.stdout.write(
26
+ `${command.dryRun ? 'Would migrate' : 'Migrated'} ${result.from} → ${result.to}: ${result.changedFiles.join(', ')}\n`,
27
+ )
28
+ } catch (error) {
22
29
  environment.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`)
23
30
  environment.exitCode = 1
24
31
  }
25
32
  }
26
33
 
27
34
  function parseCommand(argv) {
28
- const { positionals, values } = parseArgs({ args: argv, allowPositionals: true, options: {
29
- 'dry-run': { type: 'boolean' }, from: { type: 'string' }, to: { type: 'string' }, help: { short: 'h', type: 'boolean' },
30
- }, strict: true })
35
+ const { positionals, values } = parseArgs({
36
+ args: argv,
37
+ allowPositionals: true,
38
+ options: {
39
+ 'dry-run': { type: 'boolean' },
40
+ from: { type: 'string' },
41
+ to: { type: 'string' },
42
+ help: { short: 'h', type: 'boolean' },
43
+ },
44
+ strict: true,
45
+ })
31
46
  const help = Boolean(values.help || positionals[0] !== 'migrate')
32
47
  if (!help && !values.from) {
33
48
  throw new Error('--from is required.')
@@ -1,7 +1,14 @@
1
1
  import { basename, resolve } from 'node:path'
2
2
  import { parseArgs } from 'node:util'
3
3
  import * as prompts from '@clack/prompts'
4
- import { buildProjectSpecification, EXAMPLES, normalizePackageName, PACKAGE_MANAGERS, STYLE_LANGUAGES, toTitle } from '../domain/projectSpecification.mjs'
4
+ import {
5
+ buildProjectSpecification,
6
+ EXAMPLES,
7
+ normalizePackageName,
8
+ PACKAGE_MANAGERS,
9
+ STYLE_LANGUAGES,
10
+ toTitle,
11
+ } from '../domain/projectSpecification.mjs'
5
12
 
6
13
  export function parseCommandLine(args) {
7
14
  return parseArgs({
@@ -9,24 +16,24 @@ export function parseCommandLine(args) {
9
16
  args,
10
17
  options: {
11
18
  'color-scheme': { type: 'string' },
12
- 'description': { type: 'string' },
19
+ description: { type: 'string' },
13
20
  'dry-run': { type: 'boolean' },
14
- 'example': { type: 'string' },
15
- 'git': { type: 'boolean' },
16
- 'help': { short: 'h', type: 'boolean' },
17
- 'lang': { type: 'string' },
18
- 'name': { type: 'string' },
21
+ example: { type: 'string' },
22
+ git: { type: 'boolean' },
23
+ help: { short: 'h', type: 'boolean' },
24
+ lang: { type: 'string' },
25
+ name: { type: 'string' },
19
26
  'no-git': { type: 'boolean' },
20
27
  'no-install': { type: 'boolean' },
21
28
  'no-interactive': { type: 'boolean' },
22
29
  'package-manager': { type: 'string' },
23
30
  'primary-color': { type: 'string' },
24
31
  'skip-install': { type: 'boolean' },
25
- 'style': { type: 'string' },
26
- 'template': { type: 'string' },
27
- 'title': { type: 'string' },
28
- 'version': { short: 'v', type: 'boolean' },
29
- 'yes': { short: 'y', type: 'boolean' },
32
+ style: { type: 'string' },
33
+ template: { type: 'string' },
34
+ title: { type: 'string' },
35
+ version: { short: 'v', type: 'boolean' },
36
+ yes: { short: 'y', type: 'boolean' },
30
37
  },
31
38
  strict: true,
32
39
  })
@@ -42,9 +49,7 @@ export async function resolveProjectRequest(command, environment) {
42
49
  const packageManager = await resolvePackageManager(values, environment, prompt)
43
50
  const decisions = await resolveProjectDecisions(values, prompt)
44
51
  const example = values.example ?? 'clean'
45
- const style = values.style
46
- ?? await prompt?.choice('CSS Modules language', STYLE_LANGUAGES, 'css')
47
- ?? 'css'
52
+ const style = values.style ?? (await prompt?.choice('CSS Modules language', STYLE_LANGUAGES, 'css')) ?? 'css'
48
53
 
49
54
  return {
50
55
  dryRun: values['dry-run'] ?? false,
@@ -66,8 +71,7 @@ export async function resolveProjectRequest(command, environment) {
66
71
  title: identity.title,
67
72
  }),
68
73
  }
69
- }
70
- finally {
74
+ } finally {
71
75
  prompt?.close()
72
76
  }
73
77
  }
@@ -121,11 +125,13 @@ async function createPrompt(environment, values) {
121
125
  prompts.intro('create-jst')
122
126
  return {
123
127
  async choice(label, choices, fallback) {
124
- return resolvePrompt(await prompts.select({
125
- initialValue: fallback,
126
- message: label,
127
- options: choices.map(value => ({ hint: choiceHint(value), label: value, value })),
128
- }))
128
+ return resolvePrompt(
129
+ await prompts.select({
130
+ initialValue: fallback,
131
+ message: label,
132
+ options: choices.map((value) => ({ hint: choiceHint(value), label: value, value })),
133
+ }),
134
+ )
129
135
  },
130
136
  close() {
131
137
  return undefined
@@ -171,11 +177,8 @@ function validateCommand(positionals, values) {
171
177
  }
172
178
 
173
179
  async function resolveProjectIdentity(positionals, values, environment, prompt) {
174
- const directory = positionals[0]
175
- ?? await prompt?.text('Project directory', 'my-jst-app')
176
- ?? 'my-jst-app'
177
- const name = values.name
178
- ?? normalizePackageName(basename(resolve(environment.cwd(), directory)))
180
+ const directory = positionals[0] ?? (await prompt?.text('Project directory', 'my-jst-app')) ?? 'my-jst-app'
181
+ const name = values.name ?? normalizePackageName(basename(resolve(environment.cwd(), directory)))
179
182
 
180
183
  return {
181
184
  directory,
@@ -186,9 +189,7 @@ async function resolveProjectIdentity(positionals, values, environment, prompt)
186
189
 
187
190
  async function resolvePackageManager(values, environment, prompt) {
188
191
  const detected = detectPackageManager(environment.env)
189
- const requested = values['package-manager']
190
- ?? await prompt?.choice('Package manager', PACKAGE_MANAGERS, detected)
191
- ?? detected
192
+ const requested = values['package-manager'] ?? (await prompt?.choice('Package manager', PACKAGE_MANAGERS, detected)) ?? detected
192
193
 
193
194
  return requested === 'auto' ? detected : requested
194
195
  }
@@ -2,17 +2,17 @@ import { basename } from 'node:path'
2
2
  import * as prompts from '@clack/prompts'
3
3
 
4
4
  const progressMessages = {
5
- 'dependencies.installation.started': specification => `Installing dependencies with ${specification.packageManager}`,
5
+ 'dependencies.installation.started': (specification) => `Installing dependencies with ${specification.packageManager}`,
6
6
  'git.initialization.started': () => 'Initialising Git',
7
- 'package-manager.configuration.started': specification => `Preparing ${specification.packageManager}`,
8
- 'project.configuration.started': specification => `Configuring ${specification.title}`,
9
- 'template.download.started': specification => `Downloading ${specification.template}`,
7
+ 'package-manager.configuration.started': (specification) => `Preparing ${specification.packageManager}`,
8
+ 'project.configuration.started': (specification) => `Configuring ${specification.title}`,
9
+ 'template.download.started': (specification) => `Downloading ${specification.template}`,
10
10
  }
11
11
 
12
12
  export function createReporter(output, environment = {}) {
13
13
  const color = Boolean(output.isTTY) && !environment.NO_COLOR
14
- const paint = (code, value) => color ? `\u001B[${code}m${value}\u001B[0m` : value
15
- const dim = value => paint('2', value)
14
+ const paint = (code, value) => (color ? `\u001B[${code}m${value}\u001B[0m` : value)
15
+ const dim = (value) => paint('2', value)
16
16
  let activeSpinner
17
17
  let usesClack = false
18
18
 
@@ -31,12 +31,15 @@ export function createReporter(output, environment = {}) {
31
31
  if (!request.interactive) {
32
32
  prompts.intro('create-jst')
33
33
  }
34
- prompts.note([
35
- `Template: ${specification.template}`,
36
- `Package manager: ${specification.packageManager}`,
37
- `Install dependencies: ${specification.install ? 'yes' : 'no'}`,
38
- `Initialize Git: ${specification.initGit ? 'yes' : 'no'}`,
39
- ].join('\n'), specification.destination)
34
+ prompts.note(
35
+ [
36
+ `Template: ${specification.template}`,
37
+ `Package manager: ${specification.packageManager}`,
38
+ `Install dependencies: ${specification.install ? 'yes' : 'no'}`,
39
+ `Initialize Git: ${specification.initGit ? 'yes' : 'no'}`,
40
+ ].join('\n'),
41
+ specification.destination,
42
+ )
40
43
  prompts.outro('Dry run complete — no files were written.')
41
44
  return
42
45
  }
@@ -58,20 +61,20 @@ export function createReporter(output, environment = {}) {
58
61
  output.write(`\n${paint('38;5;203', 'JST')} ${dim('A foundation for replaceable layers')}\n\n`)
59
62
  },
60
63
  progress(event) {
61
- if (event.type === 'project.initialized') {
62
- return this.success(event.specification)
63
- }
64
- const message = progressMessages[event.type]
65
- if (!message) {
66
- return
67
- }
68
- if (usesClack) {
69
- activeSpinner?.stop()
70
- activeSpinner = prompts.spinner()
71
- activeSpinner.start(message(event.specification))
72
- return
73
- }
74
- output.write(`${dim(' ◌')} ${message(event.specification)}\n`)
64
+ if (event.type === 'project.initialized') {
65
+ return this.success(event.specification)
66
+ }
67
+ const message = progressMessages[event.type]
68
+ if (!message) {
69
+ return
70
+ }
71
+ if (usesClack) {
72
+ activeSpinner?.stop()
73
+ activeSpinner = prompts.spinner()
74
+ activeSpinner.start(message(event.specification))
75
+ return
76
+ }
77
+ output.write(`${dim(' ◌')} ${message(event.specification)}\n`)
75
78
  },
76
79
  success(specification) {
77
80
  renderSuccess({ activeSpinner, dim, output, paint, specification, usesClack })
@@ -107,10 +110,6 @@ function getNextSteps(specification, directory) {
107
110
  `Fast gate ${packageManager} run validate`,
108
111
  `Release gate ${packageManager} run check:release`,
109
112
  ],
110
- setup: [
111
- `cd ${directory}`,
112
- ...(install ? [] : [`${packageManager} install`]),
113
- `${packageManager} run dev`,
114
- ],
113
+ setup: [`cd ${directory}`, ...(install ? [] : [`${packageManager} install`]), `${packageManager} run dev`],
115
114
  }
116
115
  }