create-deka-app 0.0.3 → 0.0.5

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-deka-app",
3
- "version": "0.0.3",
3
+ "version": "0.0.5",
4
4
  "description": "Scaffold a new deka app",
5
5
  "bin": {
6
6
  "create-deka-app": "./index.js"
package/src/cli.js CHANGED
@@ -17,11 +17,17 @@ export function run({
17
17
  cwd = process.cwd(),
18
18
  log = console.log,
19
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).
20
26
  ownVersion = ownPackageJson.version,
21
27
  } = {}) {
22
28
  const targetArg = argv[0]
23
29
  try {
24
- return createApp({ targetArg, cwd, env, ownVersion, log })
30
+ return createApp({ targetArg, cwd, env, log })
25
31
  } catch (err) {
26
32
  if (err instanceof ScaffoldError) {
27
33
  error(err.message)
@@ -8,11 +8,17 @@
8
8
  // arm64". `npm_execpath` is a fallback for the rarer case where that header
9
9
  // is absent but the package manager's own launcher script is still visible
10
10
  // on the path it invoked us with.
11
+ // `runScript` renders the command we tell the user to run for a
12
+ // package.json script (currently always "dev"). It must work with zero
13
+ // global installs, using only what the install step above already put on
14
+ // disk -- so it is each package manager's own idiom for running a local
15
+ // script, never a bare `deka ...` invocation (deka itself only lives in
16
+ // this project's node_modules/.bin).
11
17
  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']] },
18
+ npm: { name: 'npm', install: ['npm', ['install']], runScript: (script) => `npm run ${script}` },
19
+ pnpm: { name: 'pnpm', install: ['pnpm', ['install']], runScript: (script) => `pnpm ${script}` },
20
+ yarn: { name: 'yarn', install: ['yarn', ['install']], runScript: (script) => `yarn ${script}` },
21
+ bun: { name: 'bun', install: ['bun', ['install']], runScript: (script) => `bun run ${script}` },
16
22
  }
17
23
 
18
24
  export function detectPackageManager(env = process.env) {
package/src/scaffold.js CHANGED
@@ -29,10 +29,49 @@ function dekaBinPath(targetDir, platform) {
29
29
  return path.join(targetDir, 'node_modules', '.bin', platform === 'win32' ? 'deka.cmd' : 'deka')
30
30
  }
31
31
 
32
+ // `deka init`'s own "next steps" block always starts with a line beginning
33
+ // "[init]" (verified against the real binary: `[init] DekaScript app
34
+ // ready` followed by 1-2 indented command lines). Everything from that
35
+ // line onward is deka's suggestion for what to run next, and we always
36
+ // replace it with our own -- see printNextSteps below for why. Everything
37
+ // before it (the banner, the per-file `[create] ...` lines) is left
38
+ // intact so the user still sees the scaffold happen.
39
+ function stripDekaNextSteps(output) {
40
+ if (!output) return ''
41
+ const lines = String(output).split(/\r?\n/)
42
+ const cutIndex = lines.findIndex((line) => line.startsWith('[init]'))
43
+ return cutIndex === -1 ? String(output) : lines.slice(0, cutIndex).join('\n')
44
+ }
45
+
46
+ // deka init's own next-steps text always tells the user to run a bare
47
+ // `deka ...` command, because that's correct advice for someone who ran
48
+ // `deka init` by hand with deka already on their PATH. It is wrong advice
49
+ // here: create-deka-app only ever installs deka into this project's own
50
+ // node_modules/.bin, so a bare `deka` command fails for a user who has no
51
+ // global install. We suppress deka's block (stripDekaNextSteps) and print
52
+ // our own -- the `cd` line plus the detected package manager's own idiom
53
+ // for running the "dev" script already sitting in package.json, which
54
+ // works with nothing beyond what the install step just put on disk.
55
+ function printNextSteps(log, dirName, pm) {
56
+ log('')
57
+ log(' Next steps:')
58
+ log(` cd ${dirName}`)
59
+ log(` ${pm.runScript('dev')}`)
60
+ }
61
+
62
+ export const RUNTIME_PACKAGE = '@dekaruntime/deka'
63
+
32
64
  // Builds the package.json this package writes into every scaffolded
33
65
  // project. Exported so the test suite can assert on it directly without
34
66
  // re-deriving the shape.
35
- export function buildPackageJson(dirName, ownVersion) {
67
+ //
68
+ // `runtimeVersion` must be the @dekaruntime/deka version resolved at
69
+ // scaffold time (see resolveRuntimeVersion below) -- never this package's
70
+ // own version. create-deka-app has its own 0.0.x release line and
71
+ // @dekaruntime/deka tracks deka's releases; the two are not in lockstep
72
+ // and never will be, so passing this package's own version here pins a
73
+ // version of the runtime that may not exist (see deka#1076-era bug).
74
+ export function buildPackageJson(dirName, runtimeVersion) {
36
75
  return {
37
76
  name: sanitizePackageName(dirName),
38
77
  private: true,
@@ -43,11 +82,41 @@ export function buildPackageJson(dirName, ownVersion) {
43
82
  start: 'deka start',
44
83
  },
45
84
  devDependencies: {
46
- '@dekaruntime/deka': ownVersion,
85
+ [RUNTIME_PACKAGE]: runtimeVersion,
47
86
  },
48
87
  }
49
88
  }
50
89
 
90
+ // Looks up the latest published @dekaruntime/deka version from the npm
91
+ // registry so the generated package.json pins something that actually
92
+ // exists, rather than assuming it matches create-deka-app's own version.
93
+ //
94
+ // Always shells out to `npm` for this lookup (not the detected package
95
+ // manager) -- npm ships with every Node.js install, so it is available
96
+ // regardless of whether the user ran this via npx, pnpm create, yarn
97
+ // create or bun create, and `npm view` is a read-only registry query with
98
+ // no project-local side effects.
99
+ //
100
+ // Falls back to the "latest" dist-tag -- never to a version we already
101
+ // know is wrong -- if the registry can't be reached (offline, registry
102
+ // outage, etc), and says so via `log` so the fallback is visible.
103
+ export function resolveRuntimeVersion({ spawn = spawnSync, cwd = process.cwd(), log = console.log } = {}) {
104
+ const result = spawn('npm', ['view', RUNTIME_PACKAGE, 'version'], { cwd, encoding: 'utf8' })
105
+
106
+ const version =
107
+ result && !result.error && result.status === 0 ? String(result.stdout || '').trim() : ''
108
+
109
+ if (!version) {
110
+ log(
111
+ `> Could not resolve the latest ${RUNTIME_PACKAGE} version from the npm registry ` +
112
+ '(offline, or the registry is unreachable); pinning "latest" instead.'
113
+ )
114
+ return 'latest'
115
+ }
116
+
117
+ return version
118
+ }
119
+
51
120
  /**
52
121
  * Orchestrates the scaffold. Everything the real `deka init` binary is
53
122
  * responsible for (writing app/, public/, deka.json, deka.lock, printing
@@ -61,7 +130,6 @@ export function createApp({
61
130
  targetArg,
62
131
  cwd = process.cwd(),
63
132
  env = process.env,
64
- ownVersion,
65
133
  platform = process.platform,
66
134
  arch = process.arch,
67
135
  spawn = spawnSync,
@@ -96,7 +164,9 @@ export function createApp({
96
164
  mkdirSync(targetDir, { recursive: true })
97
165
  }
98
166
 
99
- const pkg = buildPackageJson(path.basename(targetDir), ownVersion)
167
+ const runtimeVersion = resolveRuntimeVersion({ spawn, cwd: targetDir, log })
168
+
169
+ const pkg = buildPackageJson(path.basename(targetDir), runtimeVersion)
100
170
  writeFileSync(path.join(targetDir, 'package.json'), `${JSON.stringify(pkg, null, 2)}\n`)
101
171
 
102
172
  const pm = detectPackageManager(env)
@@ -128,17 +198,38 @@ export function createApp({
128
198
  )
129
199
  }
130
200
 
131
- const init = spawn(dekaBin, ['init'], { cwd: targetDir, stdio: 'inherit' })
201
+ // Invoked as `deka init <dirName>` from the *parent* directory -- the
202
+ // same shape as someone typing `deka init myapp` by hand -- rather than
203
+ // `deka init` with cwd already set to targetDir. deka init never
204
+ // overwrites a file that already exists (package.json, written above,
205
+ // survives untouched), and this shape is what makes deka's own output
206
+ // reference the right directory name if any of it leaks through.
207
+ const dirName = path.basename(targetDir)
208
+ const parentDir = path.dirname(targetDir)
209
+ const init = spawn(dekaBin, ['init', dirName], {
210
+ cwd: parentDir,
211
+ stdio: ['inherit', 'pipe', 'pipe'],
212
+ encoding: 'utf8',
213
+ })
132
214
 
133
215
  if (init.error) {
134
216
  throw new ScaffoldError(`Could not run "deka init" in ${targetDir}: ${init.error.message}`)
135
217
  }
136
218
  if (init.status !== 0) {
219
+ // Something went wrong -- show everything deka printed, unfiltered,
220
+ // so the real error is visible. Filtering only ever happens below, on
221
+ // the success path, where we know exactly what we're throwing away.
222
+ if (init.stdout) log(String(init.stdout))
223
+ if (init.stderr) log(String(init.stderr))
137
224
  throw new ScaffoldError(
138
225
  `"deka init" failed in ${targetDir} (exit code ${init.status}).`,
139
226
  init.status ?? 1
140
227
  )
141
228
  }
142
229
 
230
+ if (init.stdout) log(stripDekaNextSteps(init.stdout))
231
+ if (init.stderr) log(stripDekaNextSteps(init.stderr))
232
+ printNextSteps(log, dirName, pm)
233
+
143
234
  return 0
144
235
  }