create-deka-app 0.0.2 → 0.0.4
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 +38 -0
- package/src/package-manager.js +33 -0
- package/src/platform.js +12 -0
- package/src/scaffold.js +184 -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.4",
|
|
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,38 @@
|
|
|
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
|
+
// create-deka-app's own version. Not currently used by createApp -- the
|
|
21
|
+
// runtime version it pins in the generated package.json is resolved
|
|
22
|
+
// separately, from the npm registry, since create-deka-app's own 0.0.x
|
|
23
|
+
// line and @dekaruntime/deka's release line are not in lockstep. Kept
|
|
24
|
+
// here as the CLI's own version for whatever legitimately needs it
|
|
25
|
+
// (e.g. a future `--version` flag).
|
|
26
|
+
ownVersion = ownPackageJson.version,
|
|
27
|
+
} = {}) {
|
|
28
|
+
const targetArg = argv[0]
|
|
29
|
+
try {
|
|
30
|
+
return createApp({ targetArg, cwd, env, log })
|
|
31
|
+
} catch (err) {
|
|
32
|
+
if (err instanceof ScaffoldError) {
|
|
33
|
+
error(err.message)
|
|
34
|
+
return err.exitCode
|
|
35
|
+
}
|
|
36
|
+
throw err
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -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,184 @@
|
|
|
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
|
+
export const RUNTIME_PACKAGE = '@dekaruntime/deka'
|
|
33
|
+
|
|
34
|
+
// Builds the package.json this package writes into every scaffolded
|
|
35
|
+
// project. Exported so the test suite can assert on it directly without
|
|
36
|
+
// re-deriving the shape.
|
|
37
|
+
//
|
|
38
|
+
// `runtimeVersion` must be the @dekaruntime/deka version resolved at
|
|
39
|
+
// scaffold time (see resolveRuntimeVersion below) -- never this package's
|
|
40
|
+
// own version. create-deka-app has its own 0.0.x release line and
|
|
41
|
+
// @dekaruntime/deka tracks deka's releases; the two are not in lockstep
|
|
42
|
+
// and never will be, so passing this package's own version here pins a
|
|
43
|
+
// version of the runtime that may not exist (see deka#1076-era bug).
|
|
44
|
+
export function buildPackageJson(dirName, runtimeVersion) {
|
|
45
|
+
return {
|
|
46
|
+
name: sanitizePackageName(dirName),
|
|
47
|
+
private: true,
|
|
48
|
+
version: '0.0.0',
|
|
49
|
+
scripts: {
|
|
50
|
+
dev: 'deka dev',
|
|
51
|
+
build: 'deka build',
|
|
52
|
+
start: 'deka start',
|
|
53
|
+
},
|
|
54
|
+
devDependencies: {
|
|
55
|
+
[RUNTIME_PACKAGE]: runtimeVersion,
|
|
56
|
+
},
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Looks up the latest published @dekaruntime/deka version from the npm
|
|
61
|
+
// registry so the generated package.json pins something that actually
|
|
62
|
+
// exists, rather than assuming it matches create-deka-app's own version.
|
|
63
|
+
//
|
|
64
|
+
// Always shells out to `npm` for this lookup (not the detected package
|
|
65
|
+
// manager) -- npm ships with every Node.js install, so it is available
|
|
66
|
+
// regardless of whether the user ran this via npx, pnpm create, yarn
|
|
67
|
+
// create or bun create, and `npm view` is a read-only registry query with
|
|
68
|
+
// no project-local side effects.
|
|
69
|
+
//
|
|
70
|
+
// Falls back to the "latest" dist-tag -- never to a version we already
|
|
71
|
+
// know is wrong -- if the registry can't be reached (offline, registry
|
|
72
|
+
// outage, etc), and says so via `log` so the fallback is visible.
|
|
73
|
+
export function resolveRuntimeVersion({ spawn = spawnSync, cwd = process.cwd(), log = console.log } = {}) {
|
|
74
|
+
const result = spawn('npm', ['view', RUNTIME_PACKAGE, 'version'], { cwd, encoding: 'utf8' })
|
|
75
|
+
|
|
76
|
+
const version =
|
|
77
|
+
result && !result.error && result.status === 0 ? String(result.stdout || '').trim() : ''
|
|
78
|
+
|
|
79
|
+
if (!version) {
|
|
80
|
+
log(
|
|
81
|
+
`> Could not resolve the latest ${RUNTIME_PACKAGE} version from the npm registry ` +
|
|
82
|
+
'(offline, or the registry is unreachable); pinning "latest" instead.'
|
|
83
|
+
)
|
|
84
|
+
return 'latest'
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return version
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Orchestrates the scaffold. Everything the real `deka init` binary is
|
|
92
|
+
* responsible for (writing app/, public/, deka.json, deka.lock, printing
|
|
93
|
+
* next steps) is left to it; this only does what has to happen before that
|
|
94
|
+
* binary exists on disk.
|
|
95
|
+
*
|
|
96
|
+
* Returns 0 on success. Throws ScaffoldError (carrying an exitCode) for
|
|
97
|
+
* every expected failure; anything else is a bug and propagates.
|
|
98
|
+
*/
|
|
99
|
+
export function createApp({
|
|
100
|
+
targetArg,
|
|
101
|
+
cwd = process.cwd(),
|
|
102
|
+
env = process.env,
|
|
103
|
+
platform = process.platform,
|
|
104
|
+
arch = process.arch,
|
|
105
|
+
spawn = spawnSync,
|
|
106
|
+
log = console.log,
|
|
107
|
+
} = {}) {
|
|
108
|
+
if (!targetArg) {
|
|
109
|
+
throw new ScaffoldError(USAGE, 1)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (!isSupportedPlatform(platform, arch)) {
|
|
113
|
+
throw new ScaffoldError(
|
|
114
|
+
`create-deka-app does not support ${platformKey(platform, arch)} yet.\n` +
|
|
115
|
+
'Supported today: darwin-arm64, darwin-x64, linux-x64.\n' +
|
|
116
|
+
'Windows support is tracked at https://github.com/dekaruntime/deka/issues/1092.'
|
|
117
|
+
)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const targetDir = path.resolve(cwd, targetArg)
|
|
121
|
+
|
|
122
|
+
if (existsSync(targetDir)) {
|
|
123
|
+
const stat = statSync(targetDir)
|
|
124
|
+
if (!stat.isDirectory()) {
|
|
125
|
+
throw new ScaffoldError(`${targetDir} already exists and is not a directory.`)
|
|
126
|
+
}
|
|
127
|
+
if (readdirSync(targetDir).length > 0) {
|
|
128
|
+
throw new ScaffoldError(
|
|
129
|
+
`${targetDir} already exists and is not empty.\n` +
|
|
130
|
+
'Choose a different directory, or remove its contents first.'
|
|
131
|
+
)
|
|
132
|
+
}
|
|
133
|
+
} else {
|
|
134
|
+
mkdirSync(targetDir, { recursive: true })
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const runtimeVersion = resolveRuntimeVersion({ spawn, cwd: targetDir, log })
|
|
138
|
+
|
|
139
|
+
const pkg = buildPackageJson(path.basename(targetDir), runtimeVersion)
|
|
140
|
+
writeFileSync(path.join(targetDir, 'package.json'), `${JSON.stringify(pkg, null, 2)}\n`)
|
|
141
|
+
|
|
142
|
+
const pm = detectPackageManager(env)
|
|
143
|
+
log(`> Using ${pm.name} to install @dekaruntime/deka...`)
|
|
144
|
+
|
|
145
|
+
const [installCmd, installArgs] = pm.install
|
|
146
|
+
const install = spawn(installCmd, installArgs, { cwd: targetDir, stdio: 'inherit' })
|
|
147
|
+
|
|
148
|
+
if (install.error) {
|
|
149
|
+
throw new ScaffoldError(
|
|
150
|
+
`Could not run "${pm.name} install" in ${targetDir}: ${install.error.message}\n` +
|
|
151
|
+
`Make sure ${pm.name} is installed and on your PATH, then run it there yourself.`
|
|
152
|
+
)
|
|
153
|
+
}
|
|
154
|
+
if (install.status !== 0) {
|
|
155
|
+
throw new ScaffoldError(
|
|
156
|
+
`"${pm.name} install" failed in ${targetDir} (exit code ${install.status}).\n` +
|
|
157
|
+
'Run it there yourself to see the full error.',
|
|
158
|
+
install.status ?? 1
|
|
159
|
+
)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const dekaBin = dekaBinPath(targetDir, platform)
|
|
163
|
+
if (!existsSync(dekaBin)) {
|
|
164
|
+
throw new ScaffoldError(
|
|
165
|
+
`${dekaBin} was not found after "${pm.name} install".\n` +
|
|
166
|
+
`@dekaruntime/deka may not have published a build for ${platformKey(platform, arch)} yet, ` +
|
|
167
|
+
'or the install above did not complete. Check its output.'
|
|
168
|
+
)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const init = spawn(dekaBin, ['init'], { cwd: targetDir, stdio: 'inherit' })
|
|
172
|
+
|
|
173
|
+
if (init.error) {
|
|
174
|
+
throw new ScaffoldError(`Could not run "deka init" in ${targetDir}: ${init.error.message}`)
|
|
175
|
+
}
|
|
176
|
+
if (init.status !== 0) {
|
|
177
|
+
throw new ScaffoldError(
|
|
178
|
+
`"deka init" failed in ${targetDir} (exit code ${init.status}).`,
|
|
179
|
+
init.status ?? 1
|
|
180
|
+
)
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return 0
|
|
184
|
+
}
|