@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.
- package/README.md +44 -19
- package/dist-templates/adw-canvas2d/README.md +21 -11
- package/dist-templates/adw-canvas2d/package.json +19 -8
- package/dist-templates/adw-canvas2d/src/index.ts +6 -1
- package/dist-templates/adw-game/README.md +39 -11
- package/dist-templates/adw-game/package.json +22 -9
- package/dist-templates/adw-game/src/index.ts +6 -1
- package/dist-templates/adw-webgl/README.md +34 -11
- package/dist-templates/adw-webgl/package.json +19 -8
- package/dist-templates/adw-webgl/src/index.ts +8 -1
- package/dist-templates/cli/README.md +1 -1
- package/dist-templates/cli/package.json +5 -5
- package/dist-templates/cli/src/index.ts +14 -1
- package/dist-templates/gtk-minimal/README.md +32 -11
- package/dist-templates/gtk-minimal/package.json +17 -7
- package/dist-templates/web-server-express/README.md +1 -1
- package/dist-templates/web-server-express/package.json +5 -5
- package/dist-templates/web-server-hono/README.md +1 -1
- package/dist-templates/web-server-hono/package.json +5 -5
- package/lib/create.d.ts +31 -11
- package/lib/create.js +114 -49
- package/lib/index.d.ts +2 -1
- package/lib/index.js +46 -8
- package/lib/prompt-template.d.ts +55 -1
- package/lib/prompt-template.js +119 -20
- package/lib/runtimes.d.ts +72 -0
- package/lib/runtimes.js +141 -0
- package/lib/select.d.ts +63 -0
- package/lib/select.js +113 -0
- package/package.json +14 -3
|
@@ -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
|
|
17
|
+
"dev": "gjsify dev --runtime gjs"
|
|
18
18
|
},
|
|
19
19
|
"devDependencies": {
|
|
20
|
-
"@gjsify/cli": "^0.
|
|
21
|
-
"@gjsify/node-globals": "^0.
|
|
22
|
-
"@gjsify/rolldown-native": "^0.
|
|
23
|
-
"@gjsify/runtime": "^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 #
|
|
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
|
|
17
|
+
"dev": "gjsify dev --runtime gjs"
|
|
18
18
|
},
|
|
19
19
|
"devDependencies": {
|
|
20
|
-
"@gjsify/cli": "^0.
|
|
21
|
-
"@gjsify/node-globals": "^0.
|
|
22
|
-
"@gjsify/rolldown-native": "^0.
|
|
23
|
-
"@gjsify/runtime": "^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
|
-
|
|
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
|
-
*
|
|
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
|
-
*
|
|
13
|
-
*
|
|
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
|
-
|
|
16
|
-
/**
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
const
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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
|
-
|
|
155
|
-
|
|
156
|
-
//
|
|
157
|
-
//
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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
|
-
|
|
163
|
-
|
|
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
|
-
/**
|
|
169
|
-
function
|
|
213
|
+
/** The template manifest, or `{}` when it cannot be read. */
|
|
214
|
+
function templateManifest(template) {
|
|
170
215
|
try {
|
|
171
|
-
|
|
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,
|
|
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 (!
|
|
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
|
-
|
|
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:
|
|
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';
|
package/lib/prompt-template.d.ts
CHANGED
|
@@ -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
|
|
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>;
|