@jals1212/pi-spaces-cli 0.4.0 → 0.5.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/CHANGELOG.md +22 -0
- package/README.md +23 -7
- package/dist/cli.js +130 -24
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,28 @@ commands and options and a patch version fixes.
|
|
|
6
6
|
|
|
7
7
|
## Unreleased
|
|
8
8
|
|
|
9
|
+
## 0.5.0
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- A setup command for new spaces: `--setup <cmd>` or `PI_SPACES_SETUP`. `pi-space
|
|
14
|
+
new` runs it in the new space, and `pi-space setup <name|--every>` runs it
|
|
15
|
+
again (never in `base`). `{home}` and `{name}` become the space's folder and
|
|
16
|
+
name, and it runs with `PI_CODING_AGENT_DIR`, `PI_SPACE_HOME` and `PI_SPACE`
|
|
17
|
+
set to the space, so a program that runs Pi can install what it needs there,
|
|
18
|
+
for example `pi-space --setup 'my-launcher --home {home} setup' new work`. A
|
|
19
|
+
failed setup keeps the space and names the command to run it again.
|
|
20
|
+
- `--setup` reaches the started Pi as `PI_SPACES_SETUP`.
|
|
21
|
+
|
|
22
|
+
### Fixed
|
|
23
|
+
|
|
24
|
+
- A blank `--command` or `--dir` (only spaces) is a usage error, as an empty one
|
|
25
|
+
already was, instead of being taken as given.
|
|
26
|
+
|
|
27
|
+
### Changed
|
|
28
|
+
|
|
29
|
+
- Needs `@jals1212/pi-spaces` 0.5.0, which reserves the name `setup`.
|
|
30
|
+
|
|
9
31
|
## 0.4.0
|
|
10
32
|
|
|
11
33
|
### Added
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# pi-space
|
|
2
2
|
|
|
3
|
-
The `pi-space` command: pick a [Pi](https://pi.dev) space and start Pi in it, create, rename and remove spaces, and install, update and list the packages of a space, from your shell. It is the optional companion of the Pi extension [`@jals1212/pi-spaces`](https://github.com/jals1212/pi-spaces#readme), which holds the spaces themselves.
|
|
3
|
+
The `pi-space` command: pick a [Pi](https://pi.dev) space and start Pi in it, create, set up, rename and remove spaces, and install, update and list the packages of a space, from your shell. It is the optional companion of the Pi extension [`@jals1212/pi-spaces`](https://github.com/jals1212/pi-spaces#readme), which holds the spaces themselves.
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
@@ -12,7 +12,7 @@ Node 20 or later. The Pi extension `@jals1212/pi-spaces` must be installed in th
|
|
|
12
12
|
|
|
13
13
|
## Commands
|
|
14
14
|
|
|
15
|
-
The command words are reserved: a space can never be named `list`, `new`, `remove`, `rename`, `switch`, `update`, `install` or `
|
|
15
|
+
The command words are reserved: a space can never be named `list`, `new`, `remove`, `rename`, `switch`, `update`, `install`, `uninstall` or `setup`, so any other first word is a space name.
|
|
16
16
|
|
|
17
17
|
| Command | What it does |
|
|
18
18
|
| --- | --- |
|
|
@@ -20,7 +20,8 @@ The command words are reserved: a space can never be named `list`, `new`, `remov
|
|
|
20
20
|
| `pi-space <name> [pi args...]` | Starts Pi in that space (`base` for the home Pi starts with). |
|
|
21
21
|
| `pi-space list` | Lists the spaces. |
|
|
22
22
|
| `pi-space list <name\|--every>` | Lists the packages of a space (Pi's `list`). |
|
|
23
|
-
| `pi-space new <name>` | Creates an empty space, as `/space new <name>` does in Pi. It does not start Pi. |
|
|
23
|
+
| `pi-space new <name>` | Creates an empty space, as `/space new <name>` does in Pi, then runs the setup command in it when there is one (see [Preparing a new space](#preparing-a-new-space)). It does not start Pi. |
|
|
24
|
+
| `pi-space setup <name\|--every>` | Runs the setup command in a space, or in every space. Never in `base`. |
|
|
24
25
|
| `pi-space rename <old> <new>` | Renames a space, as `/space rename` does in Pi. It refuses what `remove` refuses (see below). |
|
|
25
26
|
| `pi-space remove <name> [--yes] [--no-backup]` | Deletes a space, as `/space remove <name>` does in Pi. It asks first (`[y/N]`), then whether to keep a backup in `<spaces folder>/.backups` (`[Y/n]`: Enter keeps one). `--yes` skips the questions and keeps a backup, and is required without a terminal; `--no-backup` deletes without one. |
|
|
26
27
|
| `pi-space update <name\|--every> [pi update options...]` | Runs Pi's `update` in a space. What follows is Pi's: `--extensions`, `--all`, a source. With nothing after the name it is Pi's default. |
|
|
@@ -50,7 +51,7 @@ A new space gets a `settings.json` that declares the extension, so `/space` work
|
|
|
50
51
|
|
|
51
52
|
## Another program that runs Pi
|
|
52
53
|
|
|
53
|
-
To start Pi through another program for one call, give `--command` and `--dir` before the rest; the words of `--command` come before `--space`. The words are split on spaces; double or single quotes keep a path with spaces in one word. Such a program usually has its own home, so `--dir` points at its spaces folder, and the Pi it starts gets that folder as `PI_SPACES_DIR` too. Nothing is exported, so a plain `pi` in the same shell is not affected:
|
|
54
|
+
To start Pi through another program for one call, give `--command` and `--dir` before the rest; the words of `--command` come before `--space`. The words are split on spaces; double or single quotes keep a path with spaces in one word. Such a program usually has its own home, so `--dir` points at its spaces folder, and the Pi it starts gets that folder as `PI_SPACES_DIR` too. Nothing is exported, so a plain `pi` in the same shell is not affected. Every command accepts these options and uses the ones it needs (`list` ignores `--command`, `rename` ignores `--setup`), so one alias can give them all:
|
|
54
55
|
|
|
55
56
|
```sh
|
|
56
57
|
pi-space --command "my-launcher --" --dir ~/.my-launcher/spaces work # → my-launcher -- --space work
|
|
@@ -71,15 +72,30 @@ export PI_SPACES_DIR=~/.my-launcher/spaces # the spaces next to that program's
|
|
|
71
72
|
| --- | --- |
|
|
72
73
|
| `PI_SPACES_COMMAND` | The program to run, split on spaces (double or single quotes keep a path with spaces in one word, as in `"/opt/my launcher/run" --`), before `--space <name>`. Default: `pi`. `--command` wins for one call. |
|
|
73
74
|
| `PI_SPACES_DIR` | The spaces folder. A relative value is taken from your home folder. Default: `spaces` next to the base home. `--dir` wins for one call; a relative `--dir` is taken from the current folder. |
|
|
75
|
+
| `PI_SPACES_SETUP` | The command that prepares a new space (see [Preparing a new space](#preparing-a-new-space)). Unset or blank: none. `--setup` wins for one call. |
|
|
74
76
|
| `PI_CODING_AGENT_DIR` | Pi's agent home; its parent holds the default spaces folder. Default: `~/.pi/agent`. |
|
|
75
77
|
|
|
78
|
+
## Preparing a new space
|
|
79
|
+
|
|
80
|
+
A new space starts clean: only a `settings.json` that declares the extension. A program that runs Pi may need its own packages in every home it opens. Give its setup command with `--setup` (or `PI_SPACES_SETUP`), and `pi-space new` runs it right after it creates the space. The words are split as for `--command`, and in each word `{home}` becomes the space's folder and `{name}` its name. The command runs with your stdio, and with `PI_CODING_AGENT_DIR` and `PI_SPACE_HOME` set to the space's folder and `PI_SPACE` to its name:
|
|
81
|
+
|
|
82
|
+
```sh
|
|
83
|
+
pi-space --setup 'my-launcher --home {home} setup' new work # create "work", then: my-launcher --home ~/.pi/spaces/work setup
|
|
84
|
+
pi-space --setup 'pi install npm:some-package' new work # plain Pi: install a package in the new space
|
|
85
|
+
pi-space --setup 'my-launcher --home {home} setup' setup --every # run it again in every space, for example after an update
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
When the setup fails, the space is kept and `pi-space` exits with the setup's status, naming `pi-space setup <name>` to run it again. `pi-space setup <name>` ends with the setup's status. With `--every` it runs in every space in order, never in `base`, each after a `== <name>` line, and exits as the package commands do. `base` is the home the program prepares itself.
|
|
89
|
+
|
|
90
|
+
Only those three variables change. A variable that points into the base home stays as it is, so a setup that needs one per space sets it itself, for example `env MY_CONFIG={home}/config my-launcher --home {home} setup`. `--setup` also reaches the Pi that `pi-space` starts as `PI_SPACES_SETUP`, so `/space new` there names `pi-space setup <name>`.
|
|
91
|
+
|
|
76
92
|
## Exit status
|
|
77
93
|
|
|
78
94
|
| Status | Meaning |
|
|
79
95
|
| --- | --- |
|
|
80
|
-
| 0 … | Pi's own exit status, for a space and for a package command in one space. |
|
|
81
|
-
| 1 | An unknown or invalid space, a name `new` or `rename` cannot use (taken, reserved or invalid), a space `remove` or `rename` cannot touch, `remove` without `--yes` and no terminal, an out-of-range pick, the program could not start, or `--every` and at least one run failed (the failed spaces are on the last line of stderr). |
|
|
82
|
-
| 2 | No arguments and no terminal to pick in, a command with the wrong number of names (`new`, `remove`, `rename`), `install` or `uninstall` without a source, a package command without a space or `--every`, or `--command` or `--
|
|
96
|
+
| 0 … | Pi's own exit status, for a space and for a package command in one space; the setup's exit status for `setup <name>` and for a `new` whose setup failed. |
|
|
97
|
+
| 1 | An unknown or invalid space, a name `new` or `rename` cannot use (taken, reserved or invalid), a space `remove` or `rename` cannot touch, `remove` without `--yes` and no terminal, an out-of-range pick, the program could not start, or `--every` and at least one run failed (the failed spaces are on the last line of stderr), or `setup base`, since setup runs only in spaces. |
|
|
98
|
+
| 2 | No arguments and no terminal to pick in, a command with the wrong number of names (`new`, `remove`, `rename`), `install` or `uninstall` without a source, a package command without a space or `--every`, or `--command`, `--dir` or `--setup` without a value (or a blank one) or given twice: the usage is printed. `setup` without a setup command: the problem is printed. The old `--new` and `--remove`: the replacing command is printed. A `--command`, `PI_SPACES_COMMAND`, `--setup` or `PI_SPACES_SETUP` with an unclosed quote: the problem is printed, and nothing is created or started. |
|
|
83
99
|
|
|
84
100
|
## More
|
|
85
101
|
|
package/dist/cli.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// The optional `pi-space` command: pick a space and start Pi in it, and manage the spaces and their packages.
|
|
2
2
|
// It only passes `--space <name>` (and, for a package command, Pi's command after it); the extension does the
|
|
3
|
-
// switch, so it
|
|
3
|
+
// switch, so it sets PI_CODING_AGENT_DIR only for the setup command, which is not Pi and does no switch.
|
|
4
4
|
import { readFileSync } from "node:fs";
|
|
5
5
|
import { homedir } from "node:os";
|
|
6
6
|
import { dirname, join, resolve } from "node:path";
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
createSpace,
|
|
16
16
|
|
|
17
17
|
findOwnDeclaration,
|
|
18
|
+
hasSetupCommand,
|
|
18
19
|
listSpaces,
|
|
19
20
|
|
|
20
21
|
removeSpace,
|
|
@@ -24,13 +25,14 @@ import {
|
|
|
24
25
|
validateName,
|
|
25
26
|
} from "@jals1212/pi-spaces/spaces";
|
|
26
27
|
|
|
27
|
-
export const USAGE = `Usage: pi-space [--command <cmd>] [--dir <path>] [<name> [pi args...] | <command> ...]
|
|
28
|
+
export const USAGE = `Usage: pi-space [--command <cmd>] [--dir <path>] [--setup <cmd>] [<name> [pi args...] | <command> ...]
|
|
28
29
|
pi-space --help | --version
|
|
29
30
|
pi-space pick a space and start Pi in it
|
|
30
31
|
pi-space <name> [pi args...] start Pi in a space ("base" for the home Pi starts with)
|
|
31
32
|
pi-space list list the spaces
|
|
32
33
|
pi-space list <name|--every> list the packages of a space
|
|
33
|
-
pi-space new <name> create a space
|
|
34
|
+
pi-space new <name> create a space, then run the setup command in it when there is one
|
|
35
|
+
pi-space setup <name|--every> run the setup command in a space, or in every space (never in base)
|
|
34
36
|
pi-space rename <old> <new> rename a space
|
|
35
37
|
pi-space remove <name> delete a space and everything in it, after asking, and keep a backup in .backups unless told not to
|
|
36
38
|
(--yes skips the questions; --no-backup deletes without a backup)
|
|
@@ -39,19 +41,21 @@ export const USAGE = `Usage: pi-space [--command <cmd>] [--dir <path>] [<name> [
|
|
|
39
41
|
pi-space uninstall <name|--every> <source> remove a package from a space (more words go to Pi)
|
|
40
42
|
pi-space --help print this help
|
|
41
43
|
pi-space --version print the version
|
|
42
|
-
--every takes the place of the name: base first, then every space in turn; a failure does not stop the rest, and the exit status is 1 if any failed.
|
|
44
|
+
--every takes the place of the name: base first (not for setup), then every space in turn; a failure does not stop the rest, and the exit status is 1 if any failed.
|
|
43
45
|
Options before the command, for this call only:
|
|
44
46
|
--command <cmd> the program to run instead of $PI_SPACES_COMMAND (default: pi), e.g. "my-launcher --"
|
|
45
47
|
--dir <path> the spaces folder instead of $PI_SPACES_DIR; the Pi it starts gets it too
|
|
48
|
+
--setup <cmd> the setup command instead of $PI_SPACES_SETUP; {home} and {name} become the space's folder and name
|
|
46
49
|
`;
|
|
47
50
|
|
|
48
51
|
/** This package's version; package.json sits one folder above both src/ and dist/. */
|
|
49
52
|
export const VERSION = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")).version;
|
|
50
53
|
|
|
51
|
-
/** --command and --
|
|
54
|
+
/** --command, --dir and --setup: for this call only, so nothing has to be exported into the shell. */
|
|
52
55
|
|
|
53
56
|
|
|
54
57
|
|
|
58
|
+
|
|
55
59
|
|
|
56
60
|
|
|
57
61
|
/** The Pi commands that pi-space runs in one space or in every space. */
|
|
@@ -76,6 +80,7 @@ const REPLACED = {
|
|
|
76
80
|
|
|
77
81
|
|
|
78
82
|
|
|
83
|
+
|
|
79
84
|
|
|
80
85
|
|
|
81
86
|
|
|
@@ -84,11 +89,11 @@ const REPLACED = {
|
|
|
84
89
|
|
|
85
90
|
|
|
86
91
|
|
|
87
|
-
const CALL_OPTIONS = { "--command": "command", "--dir": "dir" } ;
|
|
92
|
+
const CALL_OPTIONS = { "--command": "command", "--dir": "dir", "--setup": "setup" } ;
|
|
88
93
|
|
|
89
94
|
/**
|
|
90
|
-
* --command and --
|
|
91
|
-
* The commands (list, new, remove, rename, update, install, uninstall) are reserved words, so they are read
|
|
95
|
+
* --command, --dir and --setup come first; after a space name every argument belongs to Pi.
|
|
96
|
+
* The commands (list, new, setup, remove, rename, update, install, uninstall) are reserved words, so they are read
|
|
92
97
|
* before a name: the core never lets a space have one of them as its name.
|
|
93
98
|
* A space name never starts with "-", so any other option in first place is unknown, not a name.
|
|
94
99
|
*/
|
|
@@ -98,7 +103,8 @@ export function parseCli(args ) {
|
|
|
98
103
|
while (Object.hasOwn(CALL_OPTIONS, args[index] ?? "")) {
|
|
99
104
|
const key = CALL_OPTIONS[args[index] ];
|
|
100
105
|
const value = args[index + 1];
|
|
101
|
-
|
|
106
|
+
// A blank value is as good as none: it would name no program, folder or command.
|
|
107
|
+
if (!value?.trim() || options[key] !== undefined) return { kind: "usage" };
|
|
102
108
|
options[key] = value;
|
|
103
109
|
index += 2;
|
|
104
110
|
}
|
|
@@ -116,6 +122,7 @@ function parseCommand(args ) {
|
|
|
116
122
|
if (first === "list" && rest.length === 0) return { kind: "list" };
|
|
117
123
|
if (PACKAGE_VERBS.includes(first)) return parsePackages(first , rest);
|
|
118
124
|
if (first === "new") return rest.length === 1 ? { kind: "new", name: rest[0] } : { kind: "usage" };
|
|
125
|
+
if (first === "setup") return parseSetup(rest);
|
|
119
126
|
if (first === "rename") return rest.length === 2 ? { kind: "rename", from: rest[0], to: rest[1] } : { kind: "usage" };
|
|
120
127
|
if (first === "remove") {
|
|
121
128
|
const flags = rest.filter((arg) => arg.startsWith("-"));
|
|
@@ -128,6 +135,14 @@ function parseCommand(args ) {
|
|
|
128
135
|
return { kind: "launch", name: first, args: rest };
|
|
129
136
|
}
|
|
130
137
|
|
|
138
|
+
/** `setup <name>` or `setup --every`: exactly one argument. */
|
|
139
|
+
function parseSetup(rest ) {
|
|
140
|
+
if (rest.length !== 1) return { kind: "usage" };
|
|
141
|
+
if (rest[0] === "--every") return { kind: "setup", target: { every: true } };
|
|
142
|
+
if (rest[0].startsWith("-")) return { kind: "unknown-option", option: rest[0] };
|
|
143
|
+
return { kind: "setup", target: { name: rest[0] } };
|
|
144
|
+
}
|
|
145
|
+
|
|
131
146
|
/**
|
|
132
147
|
* `<verb> <name|--every> [args...]`. list takes nothing after the target; install and uninstall need a source
|
|
133
148
|
* (the first argument after it); update may have nothing, which is Pi's own default.
|
|
@@ -150,6 +165,17 @@ export function launchCommand(env , name , args )
|
|
|
150
165
|
return { program, args: [...prefix, "--space", name, ...args] };
|
|
151
166
|
}
|
|
152
167
|
|
|
168
|
+
/**
|
|
169
|
+
* The program and arguments of the setup command for one space: the template split like --command, with every
|
|
170
|
+
* {home} and {name} in each word replaced by the space's folder and name. Throws when it has an unclosed quote.
|
|
171
|
+
*/
|
|
172
|
+
export function setupCommand(template , home , name ) {
|
|
173
|
+
// Replacer functions, so a "$" in the folder or name is put in as written, never read as a pattern.
|
|
174
|
+
const fill = (word ) => word.replaceAll("{home}", () => home).replaceAll("{name}", () => name);
|
|
175
|
+
const [program = "", ...args] = splitCommand(template).map(fill);
|
|
176
|
+
return { program, args };
|
|
177
|
+
}
|
|
178
|
+
|
|
153
179
|
/**
|
|
154
180
|
* Splits a command into words on whitespace. Double or single quotes keep whitespace inside a word and are
|
|
155
181
|
* removed. There are no backslash escapes, so a Windows path in quotes keeps its backslashes.
|
|
@@ -205,7 +231,7 @@ function agentHome(env , home ) {
|
|
|
205
231
|
|
|
206
232
|
|
|
207
233
|
|
|
208
|
-
|
|
234
|
+
|
|
209
235
|
|
|
210
236
|
|
|
211
237
|
|
|
@@ -219,7 +245,7 @@ function agentHome(env , home ) {
|
|
|
219
245
|
|
|
220
246
|
|
|
221
247
|
|
|
222
|
-
|
|
248
|
+
|
|
223
249
|
|
|
224
250
|
|
|
225
251
|
|
|
@@ -256,14 +282,18 @@ export function processIo(proc = process) {
|
|
|
256
282
|
|
|
257
283
|
export async function main(argv , io ) {
|
|
258
284
|
const command = parseCli(argv);
|
|
259
|
-
if (command.dir !== undefined || command.command !== undefined) {
|
|
260
|
-
//
|
|
261
|
-
|
|
262
|
-
|
|
285
|
+
if (command.dir !== undefined || command.command !== undefined || command.setup !== undefined) {
|
|
286
|
+
// env is this call's view; the started Pi also gets --dir (absolute: a relative one is relative to where
|
|
287
|
+
// the command runs) and --setup, but not --command, which only says how to start it.
|
|
288
|
+
const forChild = {};
|
|
289
|
+
if (command.dir !== undefined) forChild.PI_SPACES_DIR = resolve(command.dir);
|
|
290
|
+
if (command.setup !== undefined) forChild.PI_SPACES_SETUP = command.setup;
|
|
291
|
+
const env = { ...io.env, ...forChild };
|
|
263
292
|
if (command.command !== undefined) env.PI_SPACES_COMMAND = command.command;
|
|
264
|
-
io = { ...io, env, childEnv:
|
|
293
|
+
io = { ...io, env, childEnv: Object.keys(forChild).length > 0 ? { ...io.env, ...forChild } : undefined };
|
|
265
294
|
}
|
|
266
295
|
const commandSource = command.command !== undefined ? "--command" : "PI_SPACES_COMMAND";
|
|
296
|
+
const setupSource = command.setup !== undefined ? "--setup" : "PI_SPACES_SETUP";
|
|
267
297
|
const dir = spacesDir(io.env, io.home);
|
|
268
298
|
switch (command.kind) {
|
|
269
299
|
case "help":
|
|
@@ -291,7 +321,9 @@ export async function main(argv , io ) {
|
|
|
291
321
|
case "packages":
|
|
292
322
|
return runPackages(command.verb, command.target, command.args, dir, io, commandSource);
|
|
293
323
|
case "new":
|
|
294
|
-
return create(command.name, dir, io);
|
|
324
|
+
return create(command.name, dir, io, setupSource);
|
|
325
|
+
case "setup":
|
|
326
|
+
return runSetup(command.target, dir, io, setupSource);
|
|
295
327
|
case "rename":
|
|
296
328
|
return rename(command.from, command.to, dir, io);
|
|
297
329
|
case "remove":
|
|
@@ -303,10 +335,44 @@ export async function main(argv , io ) {
|
|
|
303
335
|
}
|
|
304
336
|
}
|
|
305
337
|
|
|
306
|
-
|
|
338
|
+
/** Prints the reason and returns false when the setup command has an unclosed quote. */
|
|
339
|
+
function checkSetup(io , source ) {
|
|
340
|
+
try {
|
|
341
|
+
splitCommand(io.env.PI_SPACES_SETUP ?? "");
|
|
342
|
+
return true;
|
|
343
|
+
} catch (error) {
|
|
344
|
+
io.stderr.write(`pi-space: ${source}: ${(error ).message}.\n`);
|
|
345
|
+
return false;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/** Runs a setup command in a space, as that space's agent home, and returns its exit status. */
|
|
350
|
+
function spawnSetup(setup , name , home , io ) {
|
|
351
|
+
return spawnPi(setup.program, setup.args, io, { ...io.env, PI_CODING_AGENT_DIR: home, PI_SPACE: name, PI_SPACE_HOME: home });
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/** The setup command for one space, from PI_SPACES_SETUP after the call options; checkSetup has accepted it. */
|
|
355
|
+
function setupFor(name , home , io ) {
|
|
356
|
+
return setupCommand(io.env.PI_SPACES_SETUP ?? "", home, name);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
async function create(name , dir , io , setupSource ) {
|
|
360
|
+
const withSetup = hasSetupCommand(io.env);
|
|
361
|
+
// A bad setup command is reported before anything is created.
|
|
362
|
+
if (withSetup && !checkSetup(io, setupSource)) return io.exit(2);
|
|
307
363
|
try {
|
|
308
364
|
const path = createSpace(dir, name, newSpaceDeclaration(io.env, io.home));
|
|
309
|
-
io.stdout.write(`Created space "${name}" at ${path}.\
|
|
365
|
+
io.stdout.write(`Created space "${name}" at ${path}.\n`);
|
|
366
|
+
if (withSetup) {
|
|
367
|
+
const setup = setupFor(name, path, io);
|
|
368
|
+
io.stdout.write(`Setting it up: ${[setup.program, ...setup.args].join(" ")}\n`);
|
|
369
|
+
const status = await spawnSetup(setup, name, path, io);
|
|
370
|
+
if (status !== 0) {
|
|
371
|
+
io.stderr.write(`pi-space: Setup failed in space "${name}" (exit status ${status}); the space is kept. Run it again with: pi-space setup ${name}\n`);
|
|
372
|
+
return io.exit(status);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
io.stdout.write(`Start Pi in it with: pi-space ${name}\n`);
|
|
310
376
|
return io.exit(0);
|
|
311
377
|
} catch (error) {
|
|
312
378
|
// A SpaceError is a rule (a bad or taken name); anything else is the filesystem, such as no permission.
|
|
@@ -423,10 +489,13 @@ function checkSpace(name , dir , io ) {
|
|
|
423
489
|
return true;
|
|
424
490
|
}
|
|
425
491
|
|
|
426
|
-
/**
|
|
427
|
-
|
|
492
|
+
/**
|
|
493
|
+
* Runs a program with inherited stdio and returns its exit status; 1, with the reason printed, when it cannot start.
|
|
494
|
+
* `env` is the child's whole environment; undefined lets it inherit this process's. Pi gets io.childEnv.
|
|
495
|
+
*/
|
|
496
|
+
async function spawnPi(program , programArgs , io , env ) {
|
|
428
497
|
const options = { stdio: "inherit" };
|
|
429
|
-
if (
|
|
498
|
+
if (env) options.env = env;
|
|
430
499
|
const result = await io.spawn(program, programArgs, options);
|
|
431
500
|
if (result.error) {
|
|
432
501
|
io.stderr.write(`pi-space: could not start ${program}: ${result.error.message}\n`);
|
|
@@ -444,7 +513,7 @@ async function launch(name , args , dir , io , comm
|
|
|
444
513
|
io.stderr.write(`pi-space: ${commandSource}: ${(error ).message}.\n`);
|
|
445
514
|
return io.exit(2);
|
|
446
515
|
}
|
|
447
|
-
io.exit(await spawnPi(launched.program, launched.args, io));
|
|
516
|
+
io.exit(await spawnPi(launched.program, launched.args, io, io.childEnv));
|
|
448
517
|
}
|
|
449
518
|
|
|
450
519
|
/**
|
|
@@ -466,7 +535,7 @@ async function runPackages(verb , target , args
|
|
|
466
535
|
return io.exit(2);
|
|
467
536
|
}
|
|
468
537
|
if ("every" in target) io.stdout.write(`== ${name}\n`);
|
|
469
|
-
const status = await spawnPi(launched.program, launched.args, io);
|
|
538
|
+
const status = await spawnPi(launched.program, launched.args, io, io.childEnv);
|
|
470
539
|
if (!("every" in target)) return io.exit(status);
|
|
471
540
|
if (status !== 0) failed.push(name);
|
|
472
541
|
}
|
|
@@ -477,6 +546,43 @@ async function runPackages(verb , target , args
|
|
|
477
546
|
io.exit(0);
|
|
478
547
|
}
|
|
479
548
|
|
|
549
|
+
/**
|
|
550
|
+
* Runs the setup command in a space, or in every space with --every (never base), as `pi-space setup`.
|
|
551
|
+
* With --every each run gets a `== <name>` header, one failure does not stop the rest, and the status is 1 if any failed.
|
|
552
|
+
*/
|
|
553
|
+
async function runSetup(target , dir , io , setupSource ) {
|
|
554
|
+
if (!hasSetupCommand(io.env)) {
|
|
555
|
+
io.stderr.write("pi-space: There is no setup command; set PI_SPACES_SETUP or give --setup <cmd>.\n");
|
|
556
|
+
return io.exit(2);
|
|
557
|
+
}
|
|
558
|
+
if (!checkSetup(io, setupSource)) return io.exit(2);
|
|
559
|
+
if (!("every" in target)) {
|
|
560
|
+
if (target.name === BASE) {
|
|
561
|
+
io.stderr.write("pi-space: setup runs only in spaces, not in base (the home Pi starts with).\n");
|
|
562
|
+
return io.exit(1);
|
|
563
|
+
}
|
|
564
|
+
if (!checkSpace(target.name, dir, io)) return io.exit(1);
|
|
565
|
+
const home = join(dir, target.name);
|
|
566
|
+
return io.exit(await spawnSetup(setupFor(target.name, home, io), target.name, home, io));
|
|
567
|
+
}
|
|
568
|
+
const names = listSpaces(dir);
|
|
569
|
+
if (names.length === 0) {
|
|
570
|
+
io.stdout.write("No spaces yet. Create one with pi-space new <name>.\n");
|
|
571
|
+
return io.exit(0);
|
|
572
|
+
}
|
|
573
|
+
const failed = [];
|
|
574
|
+
for (const name of names) {
|
|
575
|
+
io.stdout.write(`== ${name}\n`);
|
|
576
|
+
const home = join(dir, name);
|
|
577
|
+
if ((await spawnSetup(setupFor(name, home, io), name, home, io)) !== 0) failed.push(name);
|
|
578
|
+
}
|
|
579
|
+
if (failed.length > 0) {
|
|
580
|
+
io.stderr.write(`pi-space: setup failed in: ${failed.join(", ")}\n`);
|
|
581
|
+
return io.exit(1);
|
|
582
|
+
}
|
|
583
|
+
io.exit(0);
|
|
584
|
+
}
|
|
585
|
+
|
|
480
586
|
async function pick(dir , io , commandSource ) {
|
|
481
587
|
if (!io.stdin.isTTY) {
|
|
482
588
|
io.stderr.write(USAGE);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jals1212/pi-spaces-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "The pi-space command: pick a space and start Pi in it. Companion to the Pi extension @jals1212/pi-spaces.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "jals1212",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
41
|
"cross-spawn": "^7.0.6",
|
|
42
|
-
"@jals1212/pi-spaces": "^0.
|
|
42
|
+
"@jals1212/pi-spaces": "^0.5.0"
|
|
43
43
|
},
|
|
44
44
|
"publishConfig": {
|
|
45
45
|
"access": "public",
|