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 CHANGED
@@ -15,15 +15,22 @@ yarn create deka-app myapp
15
15
  bun create deka-app myapp
16
16
  ```
17
17
 
18
- ## Status
19
-
20
- **Placeholder.** This package reserves the name while the real scaffolder is built ([deka#1091](https://github.com/dekaruntime/deka/issues/1091)). It does not scaffold a project yet; it points you at deka.
21
-
22
- Install deka directly in the meantime:
23
-
24
- ```sh
25
- curl -fsSL https://deka.gg/install.sh | sh
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
- // Placeholder. The real scaffolder (dekaruntime/deka#1091) installs the
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
- console.log('')
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.2",
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 }
@@ -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 }
@@ -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
+ }