react-x11 2.51.0 → 2.52.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 +87 -54
- package/docs/README.md +410 -0
- package/docs/accessibility.md +628 -0
- package/docs/appearance.md +416 -0
- package/docs/architecture/accessibility.md +470 -0
- package/docs/architecture/animation.md +707 -0
- package/docs/architecture/container-queries.md +525 -0
- package/docs/architecture/custom-layout.md +334 -0
- package/docs/architecture/drag-and-drop.md +941 -0
- package/docs/architecture/element-layer-contents.md +622 -0
- package/docs/architecture/frame-pacing.md +255 -0
- package/docs/architecture/grid-layout.md +358 -0
- package/docs/architecture/protocol-efficiency.md +357 -0
- package/docs/architecture/video.md +1149 -0
- package/docs/architecture/windowed-regions.md +854 -0
- package/docs/click-to-component.md +125 -0
- package/docs/clipboard.md +281 -0
- package/docs/components.md +1749 -0
- package/docs/dbus.md +277 -0
- package/docs/debugging.md +464 -0
- package/docs/desktop-calendar.md +284 -0
- package/docs/desktop.md +648 -0
- package/docs/devtools.md +250 -0
- package/docs/drag-and-drop.md +663 -0
- package/docs/ecosystem/animation.md +394 -0
- package/docs/ecosystem/data-fetching.md +201 -0
- package/docs/ecosystem/dev-tooling.md +436 -0
- package/docs/ecosystem/forms.md +313 -0
- package/docs/ecosystem/headless.md +219 -0
- package/docs/ecosystem/i18n.md +89 -0
- package/docs/ecosystem/icons.md +410 -0
- package/docs/ecosystem/layout.md +216 -0
- package/docs/ecosystem/routing.md +146 -0
- package/docs/ecosystem/state.md +361 -0
- package/docs/ecosystem/testing.md +500 -0
- package/docs/ecosystem/theming.md +540 -0
- package/docs/ecosystem.md +473 -0
- package/docs/elements.md +2762 -0
- package/docs/embedding.md +288 -0
- package/docs/events.md +1025 -0
- package/docs/extending.md +2242 -0
- package/docs/eyedropper.md +223 -0
- package/docs/filedialog.md +258 -0
- package/docs/frame.md +580 -0
- package/docs/gl.md +170 -0
- package/docs/globalmenu.md +278 -0
- package/docs/glx.md +137 -0
- package/docs/macos.md +3295 -0
- package/docs/notifications.md +164 -0
- package/docs/packaging.md +611 -0
- package/docs/permissions.md +148 -0
- package/docs/react-features.md +430 -0
- package/docs/remote.md +223 -0
- package/docs/scale.md +339 -0
- package/docs/security.md +132 -0
- package/docs/styling.md +1928 -0
- package/docs/system.md +599 -0
- package/docs/testing.md +514 -0
- package/docs/typescript.md +117 -0
- package/docs/uri-schemes.md +380 -0
- package/docs/wayland-backend.md +591 -0
- package/docs/wayland.md +545 -0
- package/docs/windows-embedding.md +271 -0
- package/docs/windows-gl.md +221 -0
- package/docs/windows-integrations.md +233 -0
- package/docs/windows.md +1404 -0
- package/package.json +10 -3
- package/src/bus.js +39 -26
- package/src/cli/create.d.ts +43 -0
- package/src/cli/create.js +305 -0
- package/src/cli/env.js +484 -0
- package/src/cli/index.js +29 -2
- package/src/cli/templates/AGENTS.template.md +195 -0
- package/src/cli/templates/DESIGN.template.md +269 -0
- package/src/desktopintegration.js +2 -1
- package/src/desktopsettings.js +193 -24
- package/src/filedialog.js +3 -3
- package/src/keyboardstate.js +28 -0
- package/src/node.d.ts +10 -10
- package/src/portal.js +31 -0
- package/src/screencolor.js +5 -10
- package/src/types/system.d.ts +8 -6
- package/src/wayland/app.js +59 -0
- package/src/wayland/clipboard.js +14 -7
- package/src/wayland/connection.js +3 -0
- package/src/wayland/context2d.js +129 -10
- package/src/wayland/protocols/index.json +6 -0
- package/src/wayland/protocols/xdg-foreign-unstable-v2.json +1 -0
- package/src/wayland/seat.js +28 -1
- package/src/wayland/swapchain.js +23 -0
- package/src/wayland/xkb.js +137 -0
package/package.json
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-x11",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.52.0",
|
|
4
4
|
"description": "react renderer with X11 as a target",
|
|
5
5
|
"main": "./src/index.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"react-x11": "./src/cli/index.js"
|
|
8
8
|
},
|
|
9
9
|
"files": [
|
|
10
|
-
"src"
|
|
10
|
+
"src",
|
|
11
|
+
"!src/cli/create-react-x11",
|
|
12
|
+
"docs/*.md",
|
|
13
|
+
"docs/*/*.md"
|
|
11
14
|
],
|
|
12
15
|
"scripts": {
|
|
13
16
|
"test": "node --import tsx --test --test-timeout=60000",
|
|
@@ -112,7 +115,7 @@
|
|
|
112
115
|
"yoga-layout": "^3.2.1"
|
|
113
116
|
},
|
|
114
117
|
"optionalDependencies": {
|
|
115
|
-
"@windowkit/appkit": "^0.30.
|
|
118
|
+
"@windowkit/appkit": "^0.30.1",
|
|
116
119
|
"@windowkit/wayland": "^3.1.1",
|
|
117
120
|
"@windowkit/win32": "^0.0.9",
|
|
118
121
|
"dbus-native": "^0.16.0",
|
|
@@ -228,6 +231,10 @@
|
|
|
228
231
|
"./frame/child": {
|
|
229
232
|
"types": "./src/frame/child.d.ts",
|
|
230
233
|
"default": "./src/frame/child.js"
|
|
234
|
+
},
|
|
235
|
+
"./create": {
|
|
236
|
+
"types": "./src/cli/create.d.ts",
|
|
237
|
+
"default": "./src/cli/create.js"
|
|
231
238
|
}
|
|
232
239
|
},
|
|
233
240
|
"types": "./src/index.d.ts"
|
package/src/bus.js
CHANGED
|
@@ -321,39 +321,52 @@ async function connect(kind, generation) {
|
|
|
321
321
|
// deliberately attaches none — an unlistened 'error' crashes the process,
|
|
322
322
|
// which is the right contract for a caller-owned connection and the wrong
|
|
323
323
|
// one for a shared one. Consumers never attach their own.
|
|
324
|
+
//
|
|
325
|
+
// It stays on through the name-ready round trip below, not just the
|
|
326
|
+
// socket's connect: an 'error' there — Bun's reader thread failing on the
|
|
327
|
+
// connection as an app's first dial meets WebAssembly compiling — found
|
|
328
|
+
// no listener between this one coming off and the death one going on.
|
|
324
329
|
let onConnect, onClose, onError;
|
|
330
|
+
const failed = new Promise((resolve, reject) => {
|
|
331
|
+
onClose = (cause) =>
|
|
332
|
+
reject(cause ?? new Error(`react-x11: the ${kind} bus closed`));
|
|
333
|
+
onError = (err) => reject(err);
|
|
334
|
+
});
|
|
335
|
+
failed.catch(() => {});
|
|
336
|
+
conn.once('close', onClose);
|
|
337
|
+
conn.on('error', onError);
|
|
325
338
|
try {
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
})
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
339
|
+
try {
|
|
340
|
+
await Promise.race([
|
|
341
|
+
new Promise((resolve) => {
|
|
342
|
+
onConnect = () => resolve();
|
|
343
|
+
conn.once('connect', onConnect);
|
|
344
|
+
}),
|
|
345
|
+
failed,
|
|
346
|
+
]);
|
|
347
|
+
} catch (cause) {
|
|
348
|
+
await discard(bus);
|
|
349
|
+
return fail(cause);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// Name-ready. `bus.name` is assigned in the Hello reply callback
|
|
353
|
+
// (dbus-native lib/bus.js:1002-1009), so it is *not* set when the
|
|
354
|
+
// constructor returns. One org.freedesktop.DBus round trip is an ordering
|
|
355
|
+
// barrier: replies arrive behind the Hello dbus-native sent first. Without
|
|
356
|
+
// it, the first consumer to derive state from the app's own identity — a
|
|
357
|
+
// portal Request path, say — races Hello and builds a path with
|
|
358
|
+
// `undefined` in it.
|
|
359
|
+
try {
|
|
360
|
+
await Promise.race([bus.listNames(), failed]);
|
|
361
|
+
} catch (cause) {
|
|
362
|
+
await discard(bus);
|
|
363
|
+
return fail(cause);
|
|
364
|
+
}
|
|
338
365
|
} finally {
|
|
339
366
|
conn.removeListener('connect', onConnect);
|
|
340
367
|
conn.removeListener('close', onClose);
|
|
341
368
|
conn.removeListener('error', onError);
|
|
342
369
|
}
|
|
343
|
-
|
|
344
|
-
// Name-ready. `bus.name` is assigned in the Hello reply callback
|
|
345
|
-
// (dbus-native lib/bus.js:1002-1009), so it is *not* set when the
|
|
346
|
-
// constructor returns. One org.freedesktop.DBus round trip is an ordering
|
|
347
|
-
// barrier: replies arrive behind the Hello dbus-native sent first. Without
|
|
348
|
-
// it, the first consumer to derive state from the app's own identity — a
|
|
349
|
-
// portal Request path, say — races Hello and builds a path with
|
|
350
|
-
// `undefined` in it.
|
|
351
|
-
try {
|
|
352
|
-
await bus.listNames();
|
|
353
|
-
} catch (cause) {
|
|
354
|
-
await discard(bus);
|
|
355
|
-
return fail(cause);
|
|
356
|
-
}
|
|
357
370
|
if (bus.name == null) {
|
|
358
371
|
await discard(bus);
|
|
359
372
|
return fail(
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/** A mistake the developer can fix from the message. */
|
|
2
|
+
export class CreateError extends Error {}
|
|
3
|
+
|
|
4
|
+
export type PackageManager = 'npm' | 'pnpm' | 'yarn' | 'bun';
|
|
5
|
+
|
|
6
|
+
export interface CreateOptions {
|
|
7
|
+
/** Install the dependencies after writing the files. Default `true`. */
|
|
8
|
+
install?: boolean;
|
|
9
|
+
/** Write AGENTS.md and CLAUDE.md, for coding agents. Default `true`. */
|
|
10
|
+
agentsMd?: boolean;
|
|
11
|
+
/** Who installs them. Default: whichever launched the command. */
|
|
12
|
+
manager?: PackageManager;
|
|
13
|
+
/** Where progress goes. Default `console.log`. */
|
|
14
|
+
log?: (line: string) => void;
|
|
15
|
+
/** Runs the install; returns its exit status. */
|
|
16
|
+
run?: (argv: string[], cwd: string) => number | null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Write a new app into `dir`; resolves to its absolute path. */
|
|
20
|
+
export function createApp(dir: string, options?: CreateOptions): string;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* `react-x11 create`'s command line, given the arguments after `create`.
|
|
24
|
+
* `command` is only what its messages call it. Returns an exit code.
|
|
25
|
+
*/
|
|
26
|
+
export function runCreate(
|
|
27
|
+
argv: string[],
|
|
28
|
+
options?: CreateOptions & { command?: string },
|
|
29
|
+
): number;
|
|
30
|
+
|
|
31
|
+
/** The package manager that launched this process, from its user agent. */
|
|
32
|
+
export function packageManagerOf(userAgent?: string): PackageManager;
|
|
33
|
+
|
|
34
|
+
/** `name` as an npm package name, or null when nothing of it survives. */
|
|
35
|
+
export function packageNameFor(name: string): string | null;
|
|
36
|
+
|
|
37
|
+
/** The new app's files, as a map from path to contents. */
|
|
38
|
+
export function appFiles(options: {
|
|
39
|
+
name: string;
|
|
40
|
+
title?: string;
|
|
41
|
+
manager?: PackageManager;
|
|
42
|
+
agentsMd?: boolean;
|
|
43
|
+
}): Record<string, string>;
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
// `react-x11 create <dir>` — a new app, ready for `npm run dev`. Also what
|
|
2
|
+
// `npm create react-x11 <dir>` runs: create-react-x11 (./create-react-x11/,
|
|
3
|
+
// published beside react-x11 at the same version) is one import of
|
|
4
|
+
// `runCreate`, so the two commands cannot drift apart.
|
|
5
|
+
//
|
|
6
|
+
// The app it writes is the shortest one that shows the whole loop: a
|
|
7
|
+
// component in its own module (which is what `react-x11 dev` hot-reloads), an
|
|
8
|
+
// entry that mounts it for a plain run, and the hot-reload toolchain already
|
|
9
|
+
// in devDependencies, so the first `npm run dev` does not stop to ask for it.
|
|
10
|
+
// Every version comes from this copy of react-x11's own manifest, which is
|
|
11
|
+
// what keeps a generated app on the majors the loader is written against.
|
|
12
|
+
//
|
|
13
|
+
// Beside the code it writes the two files a coding agent reads first:
|
|
14
|
+
// AGENTS.md (react-x11 is not react-dom, the rules that throw, where the
|
|
15
|
+
// installed docs are, how to look at what it built) and DESIGN.md (the app's
|
|
16
|
+
// design brief, and the desktop conventions in react-x11's terms), plus a
|
|
17
|
+
// CLAUDE.md that imports AGENTS.md for Claude Code, which reads only its own
|
|
18
|
+
// name. Their text is ./templates/*.template.md — named so that no agent
|
|
19
|
+
// working on this repo takes them for instructions of its own.
|
|
20
|
+
import { spawnSync } from 'node:child_process';
|
|
21
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync } from 'node:fs';
|
|
22
|
+
import { writeFileSync } from 'node:fs';
|
|
23
|
+
import { basename, join, relative, resolve } from 'node:path';
|
|
24
|
+
import { fileURLToPath } from 'node:url';
|
|
25
|
+
|
|
26
|
+
const OWN_PACKAGE = JSON.parse(
|
|
27
|
+
readFileSync(
|
|
28
|
+
fileURLToPath(new URL('../../package.json', import.meta.url)),
|
|
29
|
+
'utf8',
|
|
30
|
+
),
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
/** A mistake the developer can fix from the message: printed bare. */
|
|
34
|
+
export class CreateError extends Error {
|
|
35
|
+
constructor(message) {
|
|
36
|
+
super(message);
|
|
37
|
+
this.name = 'CreateError';
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const usage = (
|
|
42
|
+
command,
|
|
43
|
+
) => `Usage: ${command} <dir> [--no-install] [--no-agents-md]
|
|
44
|
+
|
|
45
|
+
Writes a new react-x11 app into <dir> and installs its dependencies.
|
|
46
|
+
|
|
47
|
+
Options:
|
|
48
|
+
--no-install Write the files only.
|
|
49
|
+
--no-agents-md Leave out AGENTS.md and CLAUDE.md, what coding agents
|
|
50
|
+
read. DESIGN.md, the app's design brief, is still written.
|
|
51
|
+
-h, --help Show this help.
|
|
52
|
+
`;
|
|
53
|
+
|
|
54
|
+
const FLAGS = new Set(['--no-install', '--no-agents-md']);
|
|
55
|
+
|
|
56
|
+
/** A template from ./templates, with its `{{name}}`s filled in. */
|
|
57
|
+
function template(file, values) {
|
|
58
|
+
const text = readFileSync(
|
|
59
|
+
fileURLToPath(new URL(`./templates/${file}`, import.meta.url)),
|
|
60
|
+
'utf8',
|
|
61
|
+
);
|
|
62
|
+
return text.replace(/\{\{(\w+)\}\}/g, (match, key) => {
|
|
63
|
+
if (!(key in values)) throw new Error(`${file}: no value for ${match}`);
|
|
64
|
+
return values[key];
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Files a fresh checkout or an editor leaves behind, which do not make a
|
|
69
|
+
// directory anyone's project yet.
|
|
70
|
+
const IGNORABLE = new Set(['.git', '.DS_Store', '.idea', '.vscode']);
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* The package manager that launched this command: `npm create` and `npx`,
|
|
74
|
+
* `pnpm create`, `yarn create` and `bun create` all set
|
|
75
|
+
* `npm_config_user_agent`. The new directory has no lockfile to ask, and a
|
|
76
|
+
* parent's is someone else's project.
|
|
77
|
+
*/
|
|
78
|
+
export function packageManagerOf(
|
|
79
|
+
userAgent = process.env.npm_config_user_agent,
|
|
80
|
+
) {
|
|
81
|
+
return /^(npm|pnpm|yarn|bun)\//.exec(userAgent ?? '')?.[1] ?? 'npm';
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** `name` as an npm package name, or null when nothing of it survives. */
|
|
85
|
+
export function packageNameFor(name) {
|
|
86
|
+
const cleaned = name
|
|
87
|
+
.toLowerCase()
|
|
88
|
+
.replace(/[^a-z0-9._~-]+/g, '-')
|
|
89
|
+
.replace(/^[._-]+|-+$/g, '');
|
|
90
|
+
return cleaned === '' ? null : cleaned.slice(0, 214);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** The new app's files, as a map from path to contents. */
|
|
94
|
+
export function appFiles({
|
|
95
|
+
name,
|
|
96
|
+
title = name,
|
|
97
|
+
manager = 'npm',
|
|
98
|
+
agentsMd = true,
|
|
99
|
+
}) {
|
|
100
|
+
const { version, peerDependencies, devDependencies } = OWN_PACKAGE;
|
|
101
|
+
const run = manager === 'npm' ? 'npm run' : manager;
|
|
102
|
+
const pkg = {
|
|
103
|
+
name,
|
|
104
|
+
version: '0.1.0',
|
|
105
|
+
private: true,
|
|
106
|
+
type: 'module',
|
|
107
|
+
scripts: {
|
|
108
|
+
dev: 'react-x11 dev src/App.jsx',
|
|
109
|
+
start: 'tsx src/main.jsx',
|
|
110
|
+
},
|
|
111
|
+
dependencies: {
|
|
112
|
+
react: peerDependencies.react,
|
|
113
|
+
'react-x11': `^${version}`,
|
|
114
|
+
},
|
|
115
|
+
devDependencies: {
|
|
116
|
+
'@babel/core': peerDependencies['@babel/core'],
|
|
117
|
+
'@babel/plugin-transform-react-jsx':
|
|
118
|
+
peerDependencies['@babel/plugin-transform-react-jsx'],
|
|
119
|
+
'hot-module-replacement': peerDependencies['hot-module-replacement'],
|
|
120
|
+
'react-refresh': peerDependencies['react-refresh'],
|
|
121
|
+
tsx: devDependencies.tsx,
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
const filled = { version, run };
|
|
125
|
+
const agentFiles = agentsMd
|
|
126
|
+
? {
|
|
127
|
+
'AGENTS.md': template('AGENTS.template.md', filled),
|
|
128
|
+
'CLAUDE.md': '@AGENTS.md\n',
|
|
129
|
+
}
|
|
130
|
+
: {};
|
|
131
|
+
return {
|
|
132
|
+
'package.json': `${JSON.stringify(pkg, null, 2)}\n`,
|
|
133
|
+
'.gitignore': 'node_modules\n',
|
|
134
|
+
'src/App.jsx': `import React from 'react';
|
|
135
|
+
|
|
136
|
+
export default function App() {
|
|
137
|
+
const [count, setCount] = React.useState(0);
|
|
138
|
+
return (
|
|
139
|
+
<window title=${JSON.stringify(title)} style={{ padding: 24, gap: 12 }}>
|
|
140
|
+
<text style={{ fontSize: 22 }}>Hello from react-x11</text>
|
|
141
|
+
<text>Edit src/App.jsx and save: the window updates in place.</text>
|
|
142
|
+
<box
|
|
143
|
+
onClick={() => setCount((n) => n + 1)}
|
|
144
|
+
style={{
|
|
145
|
+
alignSelf: 'flex-start',
|
|
146
|
+
padding: '6px 14px',
|
|
147
|
+
borderRadius: 6,
|
|
148
|
+
backgroundColor: '$accent',
|
|
149
|
+
color: '$accentText',
|
|
150
|
+
cursor: 'pointer',
|
|
151
|
+
':hover': { backgroundColor: '$accentHover' },
|
|
152
|
+
':active': { backgroundColor: '$accentActive' },
|
|
153
|
+
}}
|
|
154
|
+
>
|
|
155
|
+
<text>Clicked {count} times</text>
|
|
156
|
+
</box>
|
|
157
|
+
</window>
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
`,
|
|
161
|
+
'src/main.jsx': `// The entry for a plain run (${run} start). \`${run} dev\` mounts App itself.
|
|
162
|
+
import React from 'react';
|
|
163
|
+
import { createRoot } from 'react-x11';
|
|
164
|
+
import App from './App.jsx';
|
|
165
|
+
|
|
166
|
+
const root = await createRoot();
|
|
167
|
+
root.render(<App />);
|
|
168
|
+
`,
|
|
169
|
+
'README.md': `# ${title}
|
|
170
|
+
|
|
171
|
+
A [react-x11](https://sidorares.github.io/react-x11/) app.
|
|
172
|
+
|
|
173
|
+
- \`${run} dev\` — run it with hot reload: save \`src/App.jsx\` and the window
|
|
174
|
+
updates in place, state and all.
|
|
175
|
+
- \`${run} start\` — run it without.
|
|
176
|
+
|
|
177
|
+
[DESIGN.md](DESIGN.md) is the app's design brief: fill in its first section.${
|
|
178
|
+
agentsMd
|
|
179
|
+
? `
|
|
180
|
+
Coding agents read [AGENTS.md](AGENTS.md) first: how react-x11 differs from
|
|
181
|
+
react-dom, and where the docs that came with it are.`
|
|
182
|
+
: ''
|
|
183
|
+
}
|
|
184
|
+
`,
|
|
185
|
+
'DESIGN.md': template('DESIGN.template.md', filled),
|
|
186
|
+
...agentFiles,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function installCommand(manager) {
|
|
191
|
+
return manager === 'yarn' ? ['yarn'] : [manager, 'install'];
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Write a new app into `dir` and, unless `install` is false, install it.
|
|
196
|
+
* Returns the directory. `log` and `run` are the seams the tests replace.
|
|
197
|
+
*/
|
|
198
|
+
export function createApp(dir, options = {}) {
|
|
199
|
+
const {
|
|
200
|
+
install = true,
|
|
201
|
+
agentsMd = true,
|
|
202
|
+
manager = packageManagerOf(),
|
|
203
|
+
log = (line) => console.log(line),
|
|
204
|
+
run = (argv, cwd) =>
|
|
205
|
+
spawnSync(argv[0], argv.slice(1), {
|
|
206
|
+
cwd,
|
|
207
|
+
stdio: 'inherit',
|
|
208
|
+
// npm and friends are .cmd scripts there, which only a shell runs
|
|
209
|
+
shell: process.platform === 'win32',
|
|
210
|
+
}).status,
|
|
211
|
+
} = options;
|
|
212
|
+
|
|
213
|
+
const target = resolve(dir);
|
|
214
|
+
const title = basename(target);
|
|
215
|
+
const name = packageNameFor(title);
|
|
216
|
+
if (name === null) {
|
|
217
|
+
throw new CreateError(
|
|
218
|
+
`${dir}: no package name can be made from "${title}". Name the ` +
|
|
219
|
+
'directory with a letter or a digit in it.',
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
if (existsSync(target)) {
|
|
223
|
+
const present = readdirSync(target).filter((f) => !IGNORABLE.has(f));
|
|
224
|
+
if (present.length > 0) {
|
|
225
|
+
throw new CreateError(
|
|
226
|
+
`${dir} is not empty (${present.slice(0, 3).join(', ')}` +
|
|
227
|
+
`${present.length > 3 ? ', …' : ''}). Pick a new directory, or ` +
|
|
228
|
+
'empty this one.',
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
for (const [file, contents] of Object.entries(
|
|
234
|
+
appFiles({ name, title, manager, agentsMd }),
|
|
235
|
+
)) {
|
|
236
|
+
const path = join(target, file);
|
|
237
|
+
mkdirSync(join(path, '..'), { recursive: true });
|
|
238
|
+
writeFileSync(path, contents);
|
|
239
|
+
}
|
|
240
|
+
const shown = relative(process.cwd(), target) || '.';
|
|
241
|
+
log(`Created ${title} in ${target}`);
|
|
242
|
+
|
|
243
|
+
if (install) {
|
|
244
|
+
const argv = installCommand(manager);
|
|
245
|
+
log(`\nInstalling dependencies with ${manager}…`);
|
|
246
|
+
const status = run(argv, target);
|
|
247
|
+
if (status !== 0) {
|
|
248
|
+
throw new CreateError(
|
|
249
|
+
`\`${argv.join(' ')}\` failed${status == null ? '' : ` (exit ${status})`}; ` +
|
|
250
|
+
`its output is above. The files are written: run it again in ${shown}.`,
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const runScript = manager === 'npm' ? 'npm run' : manager;
|
|
256
|
+
const steps = [
|
|
257
|
+
shown === '.' ? null : `cd ${shown}`,
|
|
258
|
+
install ? null : installCommand(manager).join(' '),
|
|
259
|
+
`${runScript} dev`,
|
|
260
|
+
].filter(Boolean);
|
|
261
|
+
log(`\nNext:\n\n${steps.map((s) => ` ${s}`).join('\n')}\n`);
|
|
262
|
+
return target;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* The command line: `react-x11 create …` hands over what follows `create`,
|
|
267
|
+
* and create-react-x11 everything after its own name, so `command` is only
|
|
268
|
+
* what the messages call it. An exit code.
|
|
269
|
+
*/
|
|
270
|
+
export function runCreate(
|
|
271
|
+
argv,
|
|
272
|
+
{ command = 'react-x11 create', ...options } = {},
|
|
273
|
+
) {
|
|
274
|
+
if (argv.includes('-h') || argv.includes('--help')) {
|
|
275
|
+
process.stdout.write(usage(command));
|
|
276
|
+
return 0;
|
|
277
|
+
}
|
|
278
|
+
const flags = argv.filter((arg) => arg.startsWith('-'));
|
|
279
|
+
const unknown = flags.find((flag) => !FLAGS.has(flag));
|
|
280
|
+
if (unknown !== undefined) {
|
|
281
|
+
process.stderr.write(
|
|
282
|
+
`${command}: no option ${unknown}.\n\n${usage(command)}`,
|
|
283
|
+
);
|
|
284
|
+
return 2;
|
|
285
|
+
}
|
|
286
|
+
const dirs = argv.filter((arg) => !arg.startsWith('-'));
|
|
287
|
+
if (dirs.length !== 1) {
|
|
288
|
+
process.stderr.write(
|
|
289
|
+
`${command}: ${dirs.length === 0 ? 'which directory?' : 'one directory at a time.'}\n\n${usage(command)}`,
|
|
290
|
+
);
|
|
291
|
+
return 2;
|
|
292
|
+
}
|
|
293
|
+
try {
|
|
294
|
+
createApp(dirs[0], {
|
|
295
|
+
install: !flags.includes('--no-install'),
|
|
296
|
+
agentsMd: !flags.includes('--no-agents-md'),
|
|
297
|
+
...options,
|
|
298
|
+
});
|
|
299
|
+
return 0;
|
|
300
|
+
} catch (err) {
|
|
301
|
+
if (!(err instanceof CreateError)) throw err;
|
|
302
|
+
process.stderr.write(`${command}: ${err.message}\n`);
|
|
303
|
+
return 1;
|
|
304
|
+
}
|
|
305
|
+
}
|