create-jst 0.4.28 → 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 +5 -3
- package/src/application/projectInitializer.mjs +7 -12
- package/src/cli.mjs +4 -5
- package/src/domain/migrationCatalog.mjs +8 -3
- package/src/domain/projectSpecification.mjs +30 -5
- package/src/infrastructure/nodeMigrationGateway.mjs +8 -10
- package/src/infrastructure/nodeProjectGateway.mjs +43 -26
- package/src/jst.mjs +22 -7
- package/src/ui/command.mjs +31 -30
- package/src/ui/reporter.mjs +30 -31
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-jst",
|
|
3
|
-
"version": "0.4.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
44
|
-
|
|
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 =
|
|
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(
|
|
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(
|
|
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 = [
|
|
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.
|
|
7
|
-
export const SHOWCASE_SOURCE = 'jst-stack/jst-showcase#
|
|
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
|
|
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
|
|
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(
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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({
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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({
|
|
29
|
-
|
|
30
|
-
|
|
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.')
|
package/src/ui/command.mjs
CHANGED
|
@@ -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 {
|
|
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
|
-
|
|
19
|
+
description: { type: 'string' },
|
|
13
20
|
'dry-run': { type: 'boolean' },
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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(
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
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
|
-
|
|
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
|
}
|
package/src/ui/reporter.mjs
CHANGED
|
@@ -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
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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
|
}
|