create-deka-app 0.0.2 → 0.0.3
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 +16 -9
- package/index.js +2 -18
- package/package.json +5 -1
- package/src/cli.js +32 -0
- package/src/package-manager.js +33 -0
- package/src/platform.js +12 -0
- package/src/scaffold.js +144 -0
package/README.md
CHANGED
|
@@ -15,15 +15,22 @@ yarn create deka-app myapp
|
|
|
15
15
|
bun create deka-app myapp
|
|
16
16
|
```
|
|
17
17
|
|
|
18
|
-
##
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
18
|
+
## What it does
|
|
19
|
+
|
|
20
|
+
`npx create-deka-app myapp` takes the directory as an argument and does
|
|
21
|
+
everything non-interactively — no prompts:
|
|
22
|
+
|
|
23
|
+
1. creates `myapp/` (refuses if it already exists and is non-empty)
|
|
24
|
+
2. writes `myapp/package.json`, pinning `@dekaruntime/deka` as a
|
|
25
|
+
devDependency at this package's own version, with `dev`/`build`/`start`
|
|
26
|
+
scripts that call `deka`
|
|
27
|
+
3. runs your package manager's install (detected automatically: npm, pnpm,
|
|
28
|
+
yarn or bun)
|
|
29
|
+
4. runs `deka init` from the freshly installed binary — the real
|
|
30
|
+
scaffolder, which never overwrites a file that's already there
|
|
31
|
+
|
|
32
|
+
See [deka#1091](https://github.com/dekaruntime/deka/issues/1091) for the
|
|
33
|
+
design history.
|
|
27
34
|
|
|
28
35
|
## Platforms
|
|
29
36
|
|
package/index.js
CHANGED
|
@@ -1,20 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
// platform-matched deka binary and runs `deka init`.
|
|
4
|
-
const supported = new Set(['darwin-arm64', 'darwin-x64', 'linux-x64'])
|
|
5
|
-
const platform = `${process.platform === 'win32' ? 'win32' : process.platform}-${process.arch === 'arm64' ? 'arm64' : 'x64'}`
|
|
2
|
+
import { run } from './src/cli.js'
|
|
6
3
|
|
|
7
|
-
|
|
8
|
-
console.log(' create-deka-app does not scaffold projects yet.')
|
|
9
|
-
console.log('')
|
|
10
|
-
console.log(' deka is a runtime and language for full-stack apps: https://deka.gg')
|
|
11
|
-
console.log(' Install it today: curl -fsSL https://deka.gg/install.sh | sh')
|
|
12
|
-
console.log(' Then: deka init myapp')
|
|
13
|
-
console.log('')
|
|
14
|
-
console.log(' Progress: https://github.com/dekaruntime/deka/issues/1091')
|
|
15
|
-
console.log('')
|
|
16
|
-
if (!supported.has(platform)) {
|
|
17
|
-
console.log(` Note: ${platform} is not supported yet.`)
|
|
18
|
-
console.log(' macOS (Apple Silicon and Intel) and Linux x64 are supported; Windows is coming.')
|
|
19
|
-
console.log('')
|
|
20
|
-
}
|
|
4
|
+
process.exit(run())
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-deka-app",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3",
|
|
4
4
|
"description": "Scaffold a new deka app",
|
|
5
5
|
"bin": {
|
|
6
6
|
"create-deka-app": "./index.js"
|
|
@@ -8,9 +8,13 @@
|
|
|
8
8
|
"type": "module",
|
|
9
9
|
"files": [
|
|
10
10
|
"index.js",
|
|
11
|
+
"src",
|
|
11
12
|
"README.md",
|
|
12
13
|
"LICENSE"
|
|
13
14
|
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"test": "node --test test/*.test.js"
|
|
17
|
+
},
|
|
14
18
|
"engines": {
|
|
15
19
|
"node": ">=18"
|
|
16
20
|
},
|
package/src/cli.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs'
|
|
2
|
+
import { fileURLToPath } from 'node:url'
|
|
3
|
+
import path from 'node:path'
|
|
4
|
+
import { createApp, ScaffoldError } from './scaffold.js'
|
|
5
|
+
|
|
6
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
7
|
+
const ownPackageJson = JSON.parse(readFileSync(path.join(__dirname, '..', 'package.json'), 'utf8'))
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Entry point used by both index.js (the real CLI) and the test suite
|
|
11
|
+
* (with argv/env/cwd/log overridden). Returns the process exit code rather
|
|
12
|
+
* than calling process.exit itself, so it stays testable.
|
|
13
|
+
*/
|
|
14
|
+
export function run({
|
|
15
|
+
argv = process.argv.slice(2),
|
|
16
|
+
env = process.env,
|
|
17
|
+
cwd = process.cwd(),
|
|
18
|
+
log = console.log,
|
|
19
|
+
error = console.error,
|
|
20
|
+
ownVersion = ownPackageJson.version,
|
|
21
|
+
} = {}) {
|
|
22
|
+
const targetArg = argv[0]
|
|
23
|
+
try {
|
|
24
|
+
return createApp({ targetArg, cwd, env, ownVersion, log })
|
|
25
|
+
} catch (err) {
|
|
26
|
+
if (err instanceof ScaffoldError) {
|
|
27
|
+
error(err.message)
|
|
28
|
+
return err.exitCode
|
|
29
|
+
}
|
|
30
|
+
throw err
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// Detects which package manager invoked us, so we can shell out to the same
|
|
2
|
+
// one for the install step and tell the user which one we used.
|
|
3
|
+
//
|
|
4
|
+
// `npm_config_user_agent` is set by npm, pnpm, yarn and bun alike (it is how
|
|
5
|
+
// `npx create-deka-app`, `pnpm create deka-app`, `yarn create deka-app` and
|
|
6
|
+
// `bun create deka-app` all resolve to this package), and its first token
|
|
7
|
+
// before "/" names the tool, e.g. "pnpm/8.15.1 npm/? node/v20.11.0 darwin
|
|
8
|
+
// arm64". `npm_execpath` is a fallback for the rarer case where that header
|
|
9
|
+
// is absent but the package manager's own launcher script is still visible
|
|
10
|
+
// on the path it invoked us with.
|
|
11
|
+
const MANAGERS = {
|
|
12
|
+
npm: { name: 'npm', install: ['npm', ['install']] },
|
|
13
|
+
pnpm: { name: 'pnpm', install: ['pnpm', ['install']] },
|
|
14
|
+
yarn: { name: 'yarn', install: ['yarn', ['install']] },
|
|
15
|
+
bun: { name: 'bun', install: ['bun', ['install']] },
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function detectPackageManager(env = process.env) {
|
|
19
|
+
const userAgent = env.npm_config_user_agent
|
|
20
|
+
if (userAgent) {
|
|
21
|
+
const name = userAgent.split('/')[0].toLowerCase()
|
|
22
|
+
if (MANAGERS[name]) return MANAGERS[name]
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const execPath = env.npm_execpath || ''
|
|
26
|
+
if (/pnpm/i.test(execPath)) return MANAGERS.pnpm
|
|
27
|
+
if (/yarn/i.test(execPath)) return MANAGERS.yarn
|
|
28
|
+
if (/bun/i.test(execPath)) return MANAGERS.bun
|
|
29
|
+
|
|
30
|
+
return MANAGERS.npm
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export { MANAGERS }
|
package/src/platform.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Platforms deka ships a binary for today. Windows is dekaruntime/deka#1092.
|
|
2
|
+
const SUPPORTED = new Set(['darwin-arm64', 'darwin-x64', 'linux-x64'])
|
|
3
|
+
|
|
4
|
+
export function platformKey(platform = process.platform, arch = process.arch) {
|
|
5
|
+
return `${platform}-${arch}`
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function isSupportedPlatform(platform = process.platform, arch = process.arch) {
|
|
9
|
+
return SUPPORTED.has(platformKey(platform, arch))
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export { SUPPORTED }
|
package/src/scaffold.js
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readdirSync, statSync, writeFileSync } from 'node:fs'
|
|
2
|
+
import { spawnSync } from 'node:child_process'
|
|
3
|
+
import path from 'node:path'
|
|
4
|
+
import { detectPackageManager } from './package-manager.js'
|
|
5
|
+
import { isSupportedPlatform, platformKey } from './platform.js'
|
|
6
|
+
|
|
7
|
+
// Thrown for every expected failure. `message` is written straight to
|
|
8
|
+
// stderr, so it always says what happened and what to do about it.
|
|
9
|
+
export class ScaffoldError extends Error {
|
|
10
|
+
constructor(message, exitCode = 1) {
|
|
11
|
+
super(message)
|
|
12
|
+
this.name = 'ScaffoldError'
|
|
13
|
+
this.exitCode = exitCode
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const USAGE = 'Usage: create-deka-app <directory>\n\n npx create-deka-app myapp'
|
|
18
|
+
|
|
19
|
+
function sanitizePackageName(dirName) {
|
|
20
|
+
const name = dirName
|
|
21
|
+
.toLowerCase()
|
|
22
|
+
.replace(/[^a-z0-9._-]+/g, '-')
|
|
23
|
+
.replace(/^[._-]+/, '')
|
|
24
|
+
.slice(0, 214)
|
|
25
|
+
return name.length > 0 ? name : 'deka-app'
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function dekaBinPath(targetDir, platform) {
|
|
29
|
+
return path.join(targetDir, 'node_modules', '.bin', platform === 'win32' ? 'deka.cmd' : 'deka')
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Builds the package.json this package writes into every scaffolded
|
|
33
|
+
// project. Exported so the test suite can assert on it directly without
|
|
34
|
+
// re-deriving the shape.
|
|
35
|
+
export function buildPackageJson(dirName, ownVersion) {
|
|
36
|
+
return {
|
|
37
|
+
name: sanitizePackageName(dirName),
|
|
38
|
+
private: true,
|
|
39
|
+
version: '0.0.0',
|
|
40
|
+
scripts: {
|
|
41
|
+
dev: 'deka dev',
|
|
42
|
+
build: 'deka build',
|
|
43
|
+
start: 'deka start',
|
|
44
|
+
},
|
|
45
|
+
devDependencies: {
|
|
46
|
+
'@dekaruntime/deka': ownVersion,
|
|
47
|
+
},
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Orchestrates the scaffold. Everything the real `deka init` binary is
|
|
53
|
+
* responsible for (writing app/, public/, deka.json, deka.lock, printing
|
|
54
|
+
* next steps) is left to it; this only does what has to happen before that
|
|
55
|
+
* binary exists on disk.
|
|
56
|
+
*
|
|
57
|
+
* Returns 0 on success. Throws ScaffoldError (carrying an exitCode) for
|
|
58
|
+
* every expected failure; anything else is a bug and propagates.
|
|
59
|
+
*/
|
|
60
|
+
export function createApp({
|
|
61
|
+
targetArg,
|
|
62
|
+
cwd = process.cwd(),
|
|
63
|
+
env = process.env,
|
|
64
|
+
ownVersion,
|
|
65
|
+
platform = process.platform,
|
|
66
|
+
arch = process.arch,
|
|
67
|
+
spawn = spawnSync,
|
|
68
|
+
log = console.log,
|
|
69
|
+
} = {}) {
|
|
70
|
+
if (!targetArg) {
|
|
71
|
+
throw new ScaffoldError(USAGE, 1)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (!isSupportedPlatform(platform, arch)) {
|
|
75
|
+
throw new ScaffoldError(
|
|
76
|
+
`create-deka-app does not support ${platformKey(platform, arch)} yet.\n` +
|
|
77
|
+
'Supported today: darwin-arm64, darwin-x64, linux-x64.\n' +
|
|
78
|
+
'Windows support is tracked at https://github.com/dekaruntime/deka/issues/1092.'
|
|
79
|
+
)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const targetDir = path.resolve(cwd, targetArg)
|
|
83
|
+
|
|
84
|
+
if (existsSync(targetDir)) {
|
|
85
|
+
const stat = statSync(targetDir)
|
|
86
|
+
if (!stat.isDirectory()) {
|
|
87
|
+
throw new ScaffoldError(`${targetDir} already exists and is not a directory.`)
|
|
88
|
+
}
|
|
89
|
+
if (readdirSync(targetDir).length > 0) {
|
|
90
|
+
throw new ScaffoldError(
|
|
91
|
+
`${targetDir} already exists and is not empty.\n` +
|
|
92
|
+
'Choose a different directory, or remove its contents first.'
|
|
93
|
+
)
|
|
94
|
+
}
|
|
95
|
+
} else {
|
|
96
|
+
mkdirSync(targetDir, { recursive: true })
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const pkg = buildPackageJson(path.basename(targetDir), ownVersion)
|
|
100
|
+
writeFileSync(path.join(targetDir, 'package.json'), `${JSON.stringify(pkg, null, 2)}\n`)
|
|
101
|
+
|
|
102
|
+
const pm = detectPackageManager(env)
|
|
103
|
+
log(`> Using ${pm.name} to install @dekaruntime/deka...`)
|
|
104
|
+
|
|
105
|
+
const [installCmd, installArgs] = pm.install
|
|
106
|
+
const install = spawn(installCmd, installArgs, { cwd: targetDir, stdio: 'inherit' })
|
|
107
|
+
|
|
108
|
+
if (install.error) {
|
|
109
|
+
throw new ScaffoldError(
|
|
110
|
+
`Could not run "${pm.name} install" in ${targetDir}: ${install.error.message}\n` +
|
|
111
|
+
`Make sure ${pm.name} is installed and on your PATH, then run it there yourself.`
|
|
112
|
+
)
|
|
113
|
+
}
|
|
114
|
+
if (install.status !== 0) {
|
|
115
|
+
throw new ScaffoldError(
|
|
116
|
+
`"${pm.name} install" failed in ${targetDir} (exit code ${install.status}).\n` +
|
|
117
|
+
'Run it there yourself to see the full error.',
|
|
118
|
+
install.status ?? 1
|
|
119
|
+
)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const dekaBin = dekaBinPath(targetDir, platform)
|
|
123
|
+
if (!existsSync(dekaBin)) {
|
|
124
|
+
throw new ScaffoldError(
|
|
125
|
+
`${dekaBin} was not found after "${pm.name} install".\n` +
|
|
126
|
+
`@dekaruntime/deka may not have published a build for ${platformKey(platform, arch)} yet, ` +
|
|
127
|
+
'or the install above did not complete. Check its output.'
|
|
128
|
+
)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const init = spawn(dekaBin, ['init'], { cwd: targetDir, stdio: 'inherit' })
|
|
132
|
+
|
|
133
|
+
if (init.error) {
|
|
134
|
+
throw new ScaffoldError(`Could not run "deka init" in ${targetDir}: ${init.error.message}`)
|
|
135
|
+
}
|
|
136
|
+
if (init.status !== 0) {
|
|
137
|
+
throw new ScaffoldError(
|
|
138
|
+
`"deka init" failed in ${targetDir} (exit code ${init.status}).`,
|
|
139
|
+
init.status ?? 1
|
|
140
|
+
)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return 0
|
|
144
|
+
}
|