@gjsify/create-app 0.40.0 → 0.42.0

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.
@@ -14,13 +14,13 @@
14
14
  "start:node": "gjsify run dist/index.node.mjs --runtime node",
15
15
  "start:bun": "gjsify run dist/index.node.mjs --runtime bun",
16
16
  "start:deno": "gjsify run dist/index.node.mjs --runtime deno",
17
- "dev": "gjsify run build:gjs && gjsify run start"
17
+ "dev": "gjsify dev --runtime gjs"
18
18
  },
19
19
  "devDependencies": {
20
- "@gjsify/cli": "^0.40.0",
21
- "@gjsify/node-globals": "^0.40.0",
22
- "@gjsify/rolldown-native": "^0.40.0",
23
- "@gjsify/runtime": "^0.40.0",
20
+ "@gjsify/cli": "^0.42.0",
21
+ "@gjsify/node-globals": "^0.42.0",
22
+ "@gjsify/rolldown-native": "^0.42.0",
23
+ "@gjsify/runtime": "^0.42.0",
24
24
  "@types/express": "^5.0.6",
25
25
  "@types/node": "^25.9.2",
26
26
  "typescript": "^6.0.3"
@@ -17,7 +17,7 @@ engine the build needs when it runs under GJS — is listed explicitly in
17
17
  ## Commands
18
18
 
19
19
  ```bash
20
- npm run dev # build for GJS + run
20
+ npm run dev # watch, rebuild + relaunch on GJS
21
21
  npm run build # bundle for every runtime (dist/index.gjs.js + dist/index.node.mjs)
22
22
  npm start # run on GJS
23
23
  npm run start:node # run on Node.js
@@ -14,13 +14,13 @@
14
14
  "start:node": "gjsify run dist/index.node.mjs --runtime node",
15
15
  "start:bun": "gjsify run dist/index.node.mjs --runtime bun",
16
16
  "start:deno": "gjsify run dist/index.node.mjs --runtime deno",
17
- "dev": "gjsify run build:gjs && gjsify run start"
17
+ "dev": "gjsify dev --runtime gjs"
18
18
  },
19
19
  "devDependencies": {
20
- "@gjsify/cli": "^0.40.0",
21
- "@gjsify/node-globals": "^0.40.0",
22
- "@gjsify/rolldown-native": "^0.40.0",
23
- "@gjsify/runtime": "^0.40.0",
20
+ "@gjsify/cli": "^0.42.0",
21
+ "@gjsify/node-globals": "^0.42.0",
22
+ "@gjsify/rolldown-native": "^0.42.0",
23
+ "@gjsify/runtime": "^0.42.0",
24
24
  "@types/node": "^25.9.2",
25
25
  "typescript": "^6.0.3"
26
26
  },
package/lib/create.d.ts CHANGED
@@ -1,16 +1,9 @@
1
+ import { type TemplateInfo } from './discover-templates.js';
2
+ import { type PackageManager } from './runtimes.js';
1
3
  export { discoverTemplates, findTemplate } from './discover-templates.js';
2
4
  export type { TemplateInfo } from './discover-templates.js';
3
- /**
4
- * Package managers a scaffolded project can be installed with. All four work:
5
- * the templates declare every dependency they need explicitly — including
6
- * `@gjsify/rolldown-native`, which npm/yarn/pnpm would otherwise skip as an
7
- * optional peer of `@gjsify/cli`.
8
- *
9
- * `gjsify` is gjsify's own installer and the only one of the four that works on
10
- * a host with no Node.js at all.
11
- */
12
- export declare const PACKAGE_MANAGERS: readonly ["npm", "yarn", "pnpm", "gjsify"];
13
- export type PackageManager = (typeof PACKAGE_MANAGERS)[number];
5
+ export { PACKAGE_MANAGERS, INSTALL_ARGV, RUNTIME_PACKAGE_MANAGERS, RUNTIME_DESCRIPTIONS, packageManagersForRuntime, isKnownRuntime, hostRuntime, defaultRuntimeFor, runScriptCommand, startScriptFor, type PackageManager, } from './runtimes.js';
6
+ export { selectRuntime, selectPackageManager, type Selection } from './select.js';
14
7
  export interface CreateProjectOptions {
15
8
  projectName: string;
16
9
  /** Template short name, e.g. "gtk-minimal". If omitted, the caller is responsible for providing one via prompt. */
@@ -21,7 +14,34 @@ export interface CreateProjectOptions {
21
14
  install?: boolean;
22
15
  /** Which package manager to install with, and to name in the printed next steps. */
23
16
  packageManager?: PackageManager;
17
+ /**
18
+ * Which of the template's declared runtimes the user intends to run on.
19
+ *
20
+ * It changes no scaffolded byte — a template ships every runtime it declares
21
+ * — but it decides which package manager is legal and which of the `start:*`
22
+ * scripts the next steps name. Omitted, it resolves the same way the
23
+ * non-interactive path does: the host runtime when the template supports it.
24
+ */
25
+ runtime?: string;
24
26
  }
27
+ /**
28
+ * The bus name a project scaffolded as `projectName` announces. A D-Bus element
29
+ * holds only `[A-Za-z0-9_-]` and may not begin with a digit where an npm name may
30
+ * hold `.` and begin with one, and `Gtk.Application` refuses to construct with an
31
+ * invalid id — so `npm create @gjsify/app 2048` would fail at startup, the one
32
+ * place only a launched app can see.
33
+ */
34
+ export declare function applicationIdFor(projectName: string): string;
25
35
  /** npm package names: lowercase, digits, -, _, .; no leading . or _. */
26
36
  export declare function sanitizeProjectName(raw: string): string;
27
37
  export declare function createProject(options: CreateProjectOptions): Promise<void>;
38
+ /**
39
+ * The runtimes a template can actually be scaffolded for: what it DECLARES in
40
+ * `gjsify.example.runtimes`, minus any name this scaffolder has no installer
41
+ * mapping for.
42
+ *
43
+ * Filtering rather than trusting the declaration wholesale keeps an older
44
+ * scaffolder usable against a newer template: it offers the runtimes it can
45
+ * serve instead of listing one in the picker and then failing on the install.
46
+ */
47
+ export declare function supportedRuntimes(template: TemplateInfo): string[];
package/lib/create.js CHANGED
@@ -2,26 +2,36 @@ import { spawnSync } from 'node:child_process';
2
2
  import { readFileSync, writeFileSync, mkdirSync, existsSync, readdirSync, cpSync } from 'node:fs';
3
3
  import { resolve, join } from 'node:path';
4
4
  import { discoverTemplates, findTemplate } from './discover-templates.js';
5
+ import { INSTALL_ARGV, defaultRuntimeFor, hostRuntime, packageManagersForRuntime, runScriptCommand, startScriptFor, } from './runtimes.js';
5
6
  export { discoverTemplates, findTemplate } from './discover-templates.js';
7
+ export { PACKAGE_MANAGERS, INSTALL_ARGV, RUNTIME_PACKAGE_MANAGERS, RUNTIME_DESCRIPTIONS, packageManagersForRuntime, isKnownRuntime, hostRuntime, defaultRuntimeFor, runScriptCommand, startScriptFor, } from './runtimes.js';
8
+ // Re-exported through the package's `.` entry (which is this file, not
9
+ // `index.ts`) so `gjsify create` can reach the same decisions the standalone bin
10
+ // makes instead of re-deriving them — a second derivation is a second answer.
11
+ export { selectRuntime, selectPackageManager } from './select.js';
12
+ /** Sentinel replaced by the user's project name in every text file under the template. */
13
+ const PROJECT_NAME_SENTINEL = 'new-gjsify-app';
6
14
  /**
7
- * Package managers a scaffolded project can be installed with. All four work:
8
- * the templates declare every dependency they need explicitly — including
9
- * `@gjsify/rolldown-native`, which npm/yarn/pnpm would otherwise skip as an
10
- * optional peer of `@gjsify/cli`.
15
+ * Sentinel replaced by the scaffolded project's own application id.
11
16
  *
12
- * `gjsify` is gjsify's own installer and the only one of the four that works on
13
- * a host with no Node.js at all.
17
+ * A GApplication id is a session-bus NAME and the first app to claim it owns it.
18
+ * Every scaffolded project shipped this same literal, so starting a second one
19
+ * while the first ran made GTK treat it as a REMOTE INSTANCE: it forwarded
20
+ * `activate` to the other project's window and exited 0, with no window and no
21
+ * error of its own (measured on `gtk-minimal` + `adw-canvas2d`).
14
22
  */
15
- export const PACKAGE_MANAGERS = ['npm', 'yarn', 'pnpm', 'gjsify'];
16
- /** The `install` argv for each package manager. */
17
- const INSTALL_ARGV = {
18
- npm: ['install', '--no-audit', '--no-fund'],
19
- yarn: ['install'],
20
- pnpm: ['install'],
21
- gjsify: ['install'],
22
- };
23
- /** Sentinel replaced by the user's project name in every text file under the template. */
24
- const PROJECT_NAME_SENTINEL = 'new-gjsify-app';
23
+ const APPLICATION_ID_SENTINEL = 'org.gjsify.example';
24
+ /**
25
+ * The bus name a project scaffolded as `projectName` announces. A D-Bus element
26
+ * holds only `[A-Za-z0-9_-]` and may not begin with a digit where an npm name may
27
+ * hold `.` and begin with one, and `Gtk.Application` refuses to construct with an
28
+ * invalid id — so `npm create @gjsify/app 2048` would fail at startup, the one
29
+ * place only a launched app can see.
30
+ */
31
+ export function applicationIdFor(projectName) {
32
+ const element = projectName.replace(/[^A-Za-z0-9_-]/g, '-');
33
+ return `org.gjsify.${/^[0-9]/.test(element) ? `app-${element}` : element}`;
34
+ }
25
35
  /** File extensions we treat as text and scan for the sentinel. */
26
36
  const TEXT_FILE_EXT = new Set([
27
37
  '.json',
@@ -60,7 +70,7 @@ function isDirEmpty(path) {
60
70
  }
61
71
  export async function createProject(options) {
62
72
  const projectName = sanitizeProjectName(options.projectName);
63
- const { template, force = false, install = false, packageManager = 'npm' } = options;
73
+ const { template, force = false, install = false } = options;
64
74
  const info = findTemplate(template);
65
75
  if (!info) {
66
76
  const available = discoverTemplates()
@@ -68,6 +78,25 @@ export async function createProject(options) {
68
78
  .join(', ');
69
79
  throw new Error(`Unknown template "${template}". Available templates: ${available || '(none — run "yarn build" first)'}`);
70
80
  }
81
+ // Resolve the runtime BEFORE the manager: which managers are legal is a
82
+ // function of the runtime, so a caller passing neither must not land on npm
83
+ // for a template that only runs on gjs. A template that declares NOTHING is
84
+ // unconstrained — the reading `@gjsify/cli`'s `readDeclaredRuntimes` gives a
85
+ // null declaration — so it falls through to the host rather than to an
86
+ // arbitrary first entry.
87
+ const offered = supportedRuntimes(info);
88
+ const runtime = options.runtime ?? defaultRuntimeFor(offered, options.packageManager) ?? hostRuntime();
89
+ const managers = packageManagersForRuntime(runtime);
90
+ if (!managers) {
91
+ throw new Error(`Template "${info.name}" was asked for runtime "${runtime}", which this version of ` +
92
+ '@gjsify/create-app has no installer mapping for. Upgrade it, or pick one of: ' +
93
+ `${offered.join(', ') || '(none)'}.`);
94
+ }
95
+ const packageManager = options.packageManager ?? managers[0];
96
+ if (!managers.includes(packageManager)) {
97
+ throw new Error(`"${packageManager}" cannot install a project you intend to run on ${runtime}. ` +
98
+ `Package managers for ${runtime}: ${managers.join(', ')}.`);
99
+ }
71
100
  const targetDir = resolve(process.cwd(), projectName);
72
101
  if (existsSync(targetDir) && !isDirEmpty(targetDir) && !force) {
73
102
  console.error(`Error: Directory "${projectName}" exists and is not empty. Use --force to scaffold into it anyway.`);
@@ -79,9 +108,9 @@ export async function createProject(options) {
79
108
  console.log(`Creating new Gjsify project in ${targetDir} (template: ${info.name})...`);
80
109
  mkdirSync(targetDir, { recursive: true });
81
110
  cpSync(info.path, targetDir, { recursive: true });
82
- substituteProjectName(targetDir, projectName);
111
+ substituteTemplateSentinels(targetDir, projectName);
83
112
  if (install) {
84
- const argv = INSTALL_ARGV[packageManager];
113
+ const argv = [...INSTALL_ARGV[packageManager]];
85
114
  console.log(`Running ${packageManager} ${argv[0]}...`);
86
115
  // `shell: true` is what makes this work on Windows, where `npm` is
87
116
  // `npm.cmd`: `CreateProcess` appends only `.exe` when it searches PATH
@@ -91,7 +120,9 @@ export async function createProject(options) {
91
120
  // my-app` therefore scaffolded a project, never installed anything, and
92
121
  // blamed npm for it. `shell: true` is the exemption the invariant in
93
122
  // `@gjsify/cli`'s `utils/spawn.ts` sanctions for exactly this case. It
94
- // applies to all four managers, which all ship as `.cmd` shims there.
123
+ // applies to every manager here: npm/yarn/pnpm/gjsify ship as `.cmd`
124
+ // shims on Windows, and bun/deno as `.exe`, which the bare-name search
125
+ // finds — but the shell path is correct for both and costs nothing.
95
126
  const result = spawnSync(packageManager, argv, {
96
127
  cwd: targetDir,
97
128
  stdio: 'inherit',
@@ -107,13 +138,17 @@ export async function createProject(options) {
107
138
  console.warn(`${packageManager} install failed; re-run it manually in the project directory.`);
108
139
  }
109
140
  }
110
- printNextSteps(projectName, info, install, packageManager);
141
+ printNextSteps(projectName, info, install, packageManager, runtime);
111
142
  }
112
143
  /**
113
- * Walk the scaffolded tree and replace the sentinel in every text file.
144
+ * Walk the scaffolded tree and replace every template sentinel in every text file.
114
145
  * Skips node_modules / dist / lib and non-text files by extension.
115
146
  */
116
- function substituteProjectName(rootDir, projectName) {
147
+ function substituteTemplateSentinels(rootDir, projectName) {
148
+ const sentinels = [
149
+ [PROJECT_NAME_SENTINEL, projectName],
150
+ [APPLICATION_ID_SENTINEL, applicationIdFor(projectName)],
151
+ ];
117
152
  const skipDirs = new Set(['node_modules', 'dist', 'lib']);
118
153
  const stack = [rootDir];
119
154
  while (stack.length > 0) {
@@ -132,49 +167,79 @@ function substituteProjectName(rootDir, projectName) {
132
167
  if (!TEXT_FILE_EXT.has(ext))
133
168
  continue;
134
169
  const content = readFileSync(full, 'utf-8');
135
- if (!content.includes(PROJECT_NAME_SENTINEL))
136
- continue;
137
- writeFileSync(full, content.replaceAll(PROJECT_NAME_SENTINEL, projectName));
170
+ let replaced = content;
171
+ for (const [sentinel, value] of sentinels)
172
+ replaced = replaced.replaceAll(sentinel, value);
173
+ if (replaced !== content)
174
+ writeFileSync(full, replaced);
138
175
  }
139
176
  }
140
177
  }
141
- function printNextSteps(projectName, template, installed, packageManager) {
142
- // `npm run <script>` / `yarn <script>` / `pnpm <script>` / `gjsify run
143
- // <script>` — the same script, four spellings. Printing the one that
144
- // matches the manager the user actually chose is the whole point of
145
- // threading it this far.
146
- const run = packageManager === 'npm' ? 'npm run' : packageManager === 'gjsify' ? 'gjsify run' : packageManager;
178
+ function printNextSteps(projectName, template, installed, packageManager, runtime) {
179
+ const run = runScriptCommand(packageManager);
180
+ const scripts = templateScripts(template);
181
+ const start = startScriptFor(runtime);
147
182
  console.log('');
148
- console.log(`Project created from template "${template.name}".`);
183
+ console.log(`Project created from template "${template.name}", set up for ${runtime}.`);
149
184
  console.log('');
150
185
  console.log('Next steps:');
151
186
  console.log(` cd ${projectName}`);
152
187
  if (!installed)
153
188
  console.log(` ${packageManager} install`);
154
- console.log(` ${run} dev`);
155
- console.log('');
156
- // The runtimes the template DECLARES, not a fixed list: a GJS-only template
157
- // must not advertise `start:node`, and a portable one must not hide it.
158
- const runtimes = declaredRuntimes(template);
159
- if (runtimes.length > 1) {
160
- console.log(`Runs on ${runtimes.join(', ')}:`);
189
+ // `dev` is the gjs one-liner (build the gjs bundle, then launch it); every
190
+ // other runtime consumes the `--app node` bundle, which `dev` does not
191
+ // produce. Printing `dev` for a `--runtime deno` user sent them to a script
192
+ // that rebuilds the wrong target and starts the wrong file.
193
+ if (runtime === 'gjs' && scripts['dev']) {
194
+ console.log(` ${run} dev`);
195
+ }
196
+ else {
161
197
  console.log(` ${run} build`);
162
- for (const runtime of runtimes) {
163
- console.log(` ${run} ${runtime === 'gjs' ? 'start' : `start:${runtime}`}`);
164
- }
198
+ console.log(` ${run} ${start}`);
199
+ }
200
+ console.log('');
201
+ // The OTHER runtimes the template DECLARES, not a fixed list: a GJS-only
202
+ // template must not advertise `start:node`, and a portable one must not hide
203
+ // it. Only those whose launch script the template actually ships are listed —
204
+ // a declaration with no script behind it is not a runnable offer.
205
+ const alternatives = supportedRuntimes(template).filter((rt) => rt !== runtime && scripts[startScriptFor(rt)]);
206
+ if (alternatives.length > 0) {
207
+ console.log(`Also runs on ${alternatives.join(', ')}:`);
208
+ for (const rt of alternatives)
209
+ console.log(` ${run} ${startScriptFor(rt)}`);
165
210
  console.log('');
166
211
  }
167
212
  }
168
- /** `gjsify.example.runtimes` from the template's manifest; `[]` when unstated. */
169
- function declaredRuntimes(template) {
213
+ /** The template manifest, or `{}` when it cannot be read. */
214
+ function templateManifest(template) {
170
215
  try {
171
- const pkg = JSON.parse(readFileSync(join(template.path, 'package.json'), 'utf-8'));
172
- const runtimes = pkg.gjsify?.example?.runtimes;
173
- return Array.isArray(runtimes) ? runtimes.filter((r) => typeof r === 'string') : [];
216
+ return JSON.parse(readFileSync(join(template.path, 'package.json'), 'utf-8'));
174
217
  }
175
218
  catch {
176
219
  // A template with no readable manifest cannot be scaffolded at all —
177
220
  // `createProject` already failed by here. Nothing to advertise.
178
- return [];
221
+ return {};
179
222
  }
180
223
  }
224
+ /** The template's package scripts, keyed by name. */
225
+ function templateScripts(template) {
226
+ const scripts = templateManifest(template).scripts;
227
+ if (!scripts)
228
+ return {};
229
+ return Object.fromEntries(Object.entries(scripts).filter((entry) => typeof entry[1] === 'string'));
230
+ }
231
+ /**
232
+ * The runtimes a template can actually be scaffolded for: what it DECLARES in
233
+ * `gjsify.example.runtimes`, minus any name this scaffolder has no installer
234
+ * mapping for.
235
+ *
236
+ * Filtering rather than trusting the declaration wholesale keeps an older
237
+ * scaffolder usable against a newer template: it offers the runtimes it can
238
+ * serve instead of listing one in the picker and then failing on the install.
239
+ */
240
+ export function supportedRuntimes(template) {
241
+ const declared = templateManifest(template).gjsify?.example?.runtimes;
242
+ if (!Array.isArray(declared))
243
+ return [];
244
+ return declared.filter((rt) => typeof rt === 'string' && packageManagersForRuntime(rt) !== undefined);
245
+ }
package/lib/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
1
  #!/usr/bin/env node
2
- export { createProject, sanitizeProjectName, PACKAGE_MANAGERS, type CreateProjectOptions, type PackageManager, } from './create.js';
2
+ export { createProject, sanitizeProjectName, applicationIdFor, supportedRuntimes, PACKAGE_MANAGERS, RUNTIME_PACKAGE_MANAGERS, packageManagersForRuntime, isKnownRuntime, hostRuntime, defaultRuntimeFor, runScriptCommand, startScriptFor, type CreateProjectOptions, type PackageManager, } from './create.js';
3
+ export { selectRuntime, selectPackageManager, type Selection } from './select.js';
3
4
  export { discoverTemplates, findTemplate, type TemplateInfo } from './discover-templates.js';
package/lib/index.js CHANGED
@@ -1,11 +1,21 @@
1
1
  #!/usr/bin/env node
2
2
  import yargs from 'yargs';
3
3
  import { hideBin } from 'yargs/helpers';
4
- import { createProject, PACKAGE_MANAGERS } from './create.js';
4
+ import { createProject, supportedRuntimes } from './create.js';
5
5
  import { discoverTemplates } from './discover-templates.js';
6
- import { promptTemplate } from './prompt-template.js';
6
+ import { isCancelled, promptSetup, promptTemplate } from './prompt-template.js';
7
+ import { PACKAGE_MANAGERS } from './runtimes.js';
7
8
  const templates = discoverTemplates();
8
9
  const templateChoices = templates.map((t) => t.name);
10
+ /**
11
+ * The `--runtime` values this build can actually serve: every runtime any shipped
12
+ * template declares, in first-declaration order.
13
+ *
14
+ * DERIVED, never a second copy of the CLI's `EXAMPLE_RUNTIMES`. Accepting
15
+ * `--runtime deno` is a claim that some template runs there, and the templates
16
+ * are the only thing that can make that claim.
17
+ */
18
+ const runtimeChoices = [...new Set(templates.flatMap((t) => supportedRuntimes(t)))];
9
19
  void yargs(hideBin(process.argv))
10
20
  .scriptName('@gjsify/create-app')
11
21
  .usage('$0 [project-name]', 'Create a new Gjsify project', (yargs) => {
@@ -20,6 +30,12 @@ void yargs(hideBin(process.argv))
20
30
  describe: 'Template to scaffold from',
21
31
  type: 'string',
22
32
  choices: templateChoices.length > 0 ? templateChoices : undefined,
33
+ })
34
+ .option('runtime', {
35
+ alias: 'r',
36
+ describe: 'Runtime to set the project up for. Decides which package managers are offered and which start script the next steps name.',
37
+ type: 'string',
38
+ choices: runtimeChoices.length > 0 ? runtimeChoices : undefined,
23
39
  })
24
40
  .option('force', {
25
41
  alias: 'f',
@@ -34,33 +50,55 @@ void yargs(hideBin(process.argv))
34
50
  })
35
51
  .option('package-manager', {
36
52
  alias: 'p',
37
- describe: 'Package manager to install with, and to name in the printed next steps. `gjsify` is the only one that works on a host with no Node.js.',
53
+ describe: 'Package manager to install with, and to name in the printed next steps. Must be one the chosen runtime can install for. `gjsify` is the only one that works on a host with no Node.js.',
38
54
  type: 'string',
39
55
  choices: PACKAGE_MANAGERS,
40
- default: 'npm',
41
56
  });
42
57
  }, async (argv) => {
43
58
  const projectName = argv['project-name'];
59
+ const interactive = Boolean(process.stdin.isTTY);
44
60
  let template = argv['template'];
45
61
  if (!template) {
46
- if (!process.stdin.isTTY) {
62
+ if (!interactive) {
47
63
  const list = templateChoices.join(', ');
48
64
  console.error(`Error: --template is required in non-interactive mode. Available templates: ${list || '(none)'}`);
49
65
  // `return` — the deferred GJS exit otherwise fell through
50
66
  // into the interactive template prompt on a non-TTY.
51
67
  return process.exit(1);
52
68
  }
53
- const picked = await promptTemplate(templates);
69
+ // Ctrl+C here is an abort, not a crash. Without the guard the
70
+ // rejection escapes into yargs, which answers it with a stack
71
+ // trace and the whole `--help`.
72
+ const picked = await promptTemplate(templates).catch((error) => {
73
+ if (!isCancelled(error))
74
+ throw error;
75
+ console.error('Cancelled.');
76
+ return undefined;
77
+ });
78
+ if (picked === undefined)
79
+ return process.exit(1);
54
80
  template = picked.name;
55
81
  }
82
+ const info = templates.find((t) => t.name === template);
83
+ const setup = await promptSetup({
84
+ offered: info ? supportedRuntimes(info) : [],
85
+ runtime: argv['runtime'],
86
+ packageManager: argv['package-manager'],
87
+ install: argv['install'],
88
+ interactive,
89
+ });
90
+ if (setup === undefined)
91
+ return process.exit(1);
56
92
  await createProject({
57
93
  projectName,
58
94
  template,
95
+ runtime: setup.runtime,
59
96
  force: argv['force'],
60
97
  install: argv['install'],
61
- packageManager: argv['package-manager'],
98
+ packageManager: setup.packageManager,
62
99
  });
63
100
  })
64
101
  .help().argv;
65
- export { createProject, sanitizeProjectName, PACKAGE_MANAGERS, } from './create.js';
102
+ export { createProject, sanitizeProjectName, applicationIdFor, supportedRuntimes, PACKAGE_MANAGERS, RUNTIME_PACKAGE_MANAGERS, packageManagersForRuntime, isKnownRuntime, hostRuntime, defaultRuntimeFor, runScriptCommand, startScriptFor, } from './create.js';
103
+ export { selectRuntime, selectPackageManager } from './select.js';
66
104
  export { discoverTemplates, findTemplate } from './discover-templates.js';
@@ -1,6 +1,60 @@
1
1
  import type { TemplateInfo } from './discover-templates.js';
2
+ import { type PackageManager } from './runtimes.js';
3
+ /** `true` when `error` is a picker the user aborted with Ctrl+C. */
4
+ export declare function isCancelled(error: unknown): boolean;
5
+ /** One selectable row: the value returned, plus how it is drawn. */
6
+ export interface Choice<T> {
7
+ value: T;
8
+ label: string;
9
+ /** Dimmed text after the label. Empty string draws nothing. */
10
+ description: string;
11
+ }
2
12
  /**
3
- * Interactive template picker. Returns the selected TemplateInfo.
13
+ * Interactive arrow-key picker. Returns the selected value.
4
14
  * Throws if stdin is not a TTY — caller should check process.stdin.isTTY first.
15
+ *
16
+ * `initial` is the index the cursor starts on, so a prompt can open on the
17
+ * answer the tool would have picked anyway (the host runtime, say) and Enter
18
+ * alone is a valid response.
5
19
  */
20
+ export declare function promptChoice<T>(title: string, choices: readonly Choice<T>[], initial?: number): Promise<T>;
21
+ /** Interactive template picker. Returns the selected TemplateInfo. */
6
22
  export declare function promptTemplate(templates: TemplateInfo[]): Promise<TemplateInfo>;
23
+ /**
24
+ * Interactive runtime picker, offering only what the chosen template declares.
25
+ *
26
+ * `initial` is the runtime `defaultRuntimeFor()` resolved to; it is passed in
27
+ * rather than recomputed so the prompt opens on exactly the value the non-TTY
28
+ * path would have used.
29
+ */
30
+ export declare function promptRuntime(runtimes: readonly string[], initial: string): Promise<string>;
31
+ /** Interactive package-manager picker for the runtime already chosen. */
32
+ export declare function promptPackageManager(managers: readonly PackageManager[], initial: PackageManager): Promise<PackageManager>;
33
+ /** The two answers `createProject` needs beyond the template. */
34
+ export interface Setup {
35
+ runtime: string;
36
+ packageManager: PackageManager;
37
+ }
38
+ export interface SetupQuestion {
39
+ /** The runtimes the chosen template declares AND this scaffolder can serve. */
40
+ offered: readonly string[];
41
+ /** `--runtime`, when given. */
42
+ runtime?: string;
43
+ /** `--package-manager`, when given. */
44
+ packageManager?: PackageManager;
45
+ /** Whether `--install` is set — i.e. whether the manager choice runs something. */
46
+ install: boolean;
47
+ /** Whether stdin is a TTY, i.e. whether a prompt is possible at all. */
48
+ interactive: boolean;
49
+ }
50
+ /**
51
+ * Settle runtime and package manager: flags, then prompts, then announced
52
+ * defaults — the runtime first, because it decides which managers are legal.
53
+ *
54
+ * `undefined` means "exit non-zero, the reason is printed". Lives here rather
55
+ * than in either bin because BOTH surfaces ask these two questions — `npm create
56
+ * @gjsify/app` and `gjsify create`. A copy in one of them is a second answer to
57
+ * the same question, which is exactly what let `gjsify create --install` reach
58
+ * for npm on its own while the standalone bin refused to guess.
59
+ */
60
+ export declare function promptSetup(question: SetupQuestion): Promise<Setup | undefined>;