@grayspar/cli 0.0.4 → 0.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/access.js +3 -3
- package/dist/commands/deploy/deploy.js +1 -1
- package/dist/commands/env.js +2 -2
- package/dist/commands/link.js +2 -2
- package/dist/commands/login/sign-in-callback.js +2 -3
- package/dist/commands/logout.js +1 -1
- package/dist/commands/logs.js +1 -1
- package/dist/commands/restart.js +1 -1
- package/dist/commands/service.js +22 -1
- package/dist/commands/status.js +1 -1
- package/dist/commands/volume.js +4 -4
- package/dist/commands/whoami.js +1 -1
- package/dist/index.js +1 -56
- package/dist/lib/retry-transient.js +2 -3
- package/dist/lib/services-table.js +2 -0
- package/dist/program.js +56 -0
- package/package.json +1 -1
package/dist/commands/access.js
CHANGED
|
@@ -87,17 +87,17 @@ accessCommand
|
|
|
87
87
|
accessCommand
|
|
88
88
|
.command('add')
|
|
89
89
|
.argument('<targets...>', 'email addresses, or @company.example for everyone at a company')
|
|
90
|
-
.description('let
|
|
90
|
+
.description('let people or a whole company open a project')
|
|
91
91
|
.action(commandAction((format, _command, targets) => addAccess(targets, format)));
|
|
92
92
|
accessCommand
|
|
93
93
|
.command('remove')
|
|
94
94
|
.argument('<target>', 'an email address, or @company.example')
|
|
95
|
-
.description(
|
|
95
|
+
.description("remove a person or company from a project's list")
|
|
96
96
|
.action(commandAction((format, _command, target) => removeAccess(target, format)));
|
|
97
97
|
accessCommand
|
|
98
98
|
.command('set')
|
|
99
99
|
.addOption(new Option('--mode <mode>', 'restricted lets in the people and companies on the list, open lets in anyone with the link')
|
|
100
100
|
.choices(['restricted', 'open'])
|
|
101
101
|
.makeOptionMandatory())
|
|
102
|
-
.description('choose whether the list
|
|
102
|
+
.description('choose whether only the list or anyone with the link can open a project')
|
|
103
103
|
.action(commandAction((format, command) => setGeneralAccess(command.opts().mode, format)));
|
|
@@ -179,7 +179,7 @@ async function printOutcome(api, project, outcome) {
|
|
|
179
179
|
}
|
|
180
180
|
export const deployCommand = new Command('deploy')
|
|
181
181
|
.argument('[folder]', 'deploy only the files under this folder of the project', '.')
|
|
182
|
-
.description('build a
|
|
182
|
+
.description('build a folder and put it online as a project')
|
|
183
183
|
.option('-y, --yes', 'take the plan as shown without asking')
|
|
184
184
|
.option('--name <name>', 'create a new project with this name')
|
|
185
185
|
.option('--no-env', 'deploy without reading any .env file')
|
package/dist/commands/env.js
CHANGED
|
@@ -61,11 +61,11 @@ envCommand
|
|
|
61
61
|
.command('set')
|
|
62
62
|
.argument('<pairs...>', 'variables written as KEY=VALUE')
|
|
63
63
|
.addOption(serviceOption('which service to set them on'))
|
|
64
|
-
.description('
|
|
64
|
+
.description('set variables on a service')
|
|
65
65
|
.action(commandAction((format, command, pairs) => setVariables(pairs, serviceNameOf(command), format)));
|
|
66
66
|
envCommand
|
|
67
67
|
.command('remove')
|
|
68
68
|
.argument('<keys...>', 'the names of the variables to remove')
|
|
69
69
|
.addOption(serviceOption('which service to change'))
|
|
70
|
-
.description('remove
|
|
70
|
+
.description('remove variables from a service')
|
|
71
71
|
.action(commandAction((format, command, keys) => removeVariables(keys, serviceNameOf(command), format)));
|
package/dist/commands/link.js
CHANGED
|
@@ -21,8 +21,8 @@ async function unlink(format) {
|
|
|
21
21
|
printResult(format, { status: 'ok' }, () => receipt(`Unlinked ${found.folder}`));
|
|
22
22
|
}
|
|
23
23
|
export const linkCommand = new Command('link')
|
|
24
|
-
.description('
|
|
24
|
+
.description('link this folder to a project')
|
|
25
25
|
.action(commandAction((format) => link(format)));
|
|
26
26
|
export const unlinkCommand = new Command('unlink')
|
|
27
|
-
.description('
|
|
27
|
+
.description('unlink this folder from its project')
|
|
28
28
|
.action(commandAction((format) => unlink(format)));
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
/* Listens on a port the machine picks
|
|
2
|
-
|
|
3
|
-
the CLI has traded the code, so what its page says is true. */
|
|
1
|
+
/* Listens on a port the machine picks for one redirect, then closes. The verifier that makes the browser's code usable
|
|
2
|
+
never leaves this process, and the browser hears nothing until the code is traded, so its page tells the truth. */
|
|
4
3
|
import { createServer } from 'node:http';
|
|
5
4
|
function browserPage(mark, markColor, title, detail) {
|
|
6
5
|
return `<!doctype html><meta charset="utf-8"><meta name="color-scheme" content="light dark">
|
package/dist/commands/logout.js
CHANGED
|
@@ -14,5 +14,5 @@ async function logout(format) {
|
|
|
14
14
|
: `${receipt('Signed out on this machine')}\n${warning("Grayspar couldn't be reached, so this sign-in stays valid there. Sign it out under Ways to sign in in the dashboard.")}`);
|
|
15
15
|
}
|
|
16
16
|
export const logoutCommand = new Command('logout')
|
|
17
|
-
.description('
|
|
17
|
+
.description('sign out on this machine')
|
|
18
18
|
.action(commandAction((format) => logout(format)));
|
package/dist/commands/logs.js
CHANGED
|
@@ -98,7 +98,7 @@ async function printBuildLogs(api, format, follow) {
|
|
|
98
98
|
}
|
|
99
99
|
}
|
|
100
100
|
export const logsCommand = new Command('logs')
|
|
101
|
-
.description(
|
|
101
|
+
.description("show a service's logs, or with --build its last build log")
|
|
102
102
|
.addOption(serviceOption('which service to read'))
|
|
103
103
|
.option('--build', 'show the log from the last build instead')
|
|
104
104
|
.option('-f', 'keep printing new lines as they arrive')
|
package/dist/commands/restart.js
CHANGED
|
@@ -16,6 +16,6 @@ async function restart(serviceName, format) {
|
|
|
16
16
|
printResult(format, { status: 'restarting', project: project.name, service: service.name }, () => `Restarting ${service.name}.\nTo follow it, run\n${follow}`);
|
|
17
17
|
}
|
|
18
18
|
export const restartCommand = new Command('restart')
|
|
19
|
-
.description(
|
|
19
|
+
.description("start a fresh copy of a service's running version")
|
|
20
20
|
.addOption(serviceOption('which service to restart'))
|
|
21
21
|
.action(commandAction((format, command) => restart(serviceNameOf(command), format)));
|
package/dist/commands/service.js
CHANGED
|
@@ -4,6 +4,7 @@ import { dataOrThrow, signedInClient } from '../lib/client.js';
|
|
|
4
4
|
import { commandAction } from '../lib/command-helpers.js';
|
|
5
5
|
import { CliError } from '../lib/errors.js';
|
|
6
6
|
import { printResult } from '../lib/output.js';
|
|
7
|
+
import { askText } from '../lib/prompt.js';
|
|
7
8
|
import { resolveService } from '../lib/resolve-service.js';
|
|
8
9
|
import { receipt } from '../lib/ui.js';
|
|
9
10
|
function receiptsFor(service, change) {
|
|
@@ -38,7 +39,21 @@ async function setService(serviceName, change, format) {
|
|
|
38
39
|
];
|
|
39
40
|
printResult(format, { status: 'ok' }, () => lines.join('\n'));
|
|
40
41
|
}
|
|
41
|
-
|
|
42
|
+
async function removeService(serviceName, skipAsking, format) {
|
|
43
|
+
const api = await signedInClient();
|
|
44
|
+
const { project, service } = await resolveService(api, serviceName, '<name>');
|
|
45
|
+
if (!skipAsking) {
|
|
46
|
+
const typed = await askText(`Type ${service.name} to remove it with its volume and variables`, null, '--yes');
|
|
47
|
+
if (typed !== service.name) {
|
|
48
|
+
throw new CliError('name_mismatch', `That isn't ${service.name}. The service stays.`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
await dataOrThrow(api.DELETE('/api/projects/{projectId}/services/{serviceId}', {
|
|
52
|
+
params: { path: { projectId: project.id, serviceId: service.id } },
|
|
53
|
+
}));
|
|
54
|
+
printResult(format, { status: 'removed', service: service.name, project: project.name }, () => `${receipt(`Removed service ${service.name} from ${project.name}`)}\nAny future grayspar deploy will redeploy the ${service.name} service.`);
|
|
55
|
+
}
|
|
56
|
+
export const serviceCommand = new Command('service').description('change where a service answers and how it is built, or remove it');
|
|
42
57
|
serviceCommand
|
|
43
58
|
.command('set')
|
|
44
59
|
.argument('[name]', 'the service to change, left out when the project has one')
|
|
@@ -61,3 +76,9 @@ serviceCommand
|
|
|
61
76
|
};
|
|
62
77
|
return setService(name, change, format);
|
|
63
78
|
}));
|
|
79
|
+
serviceCommand
|
|
80
|
+
.command('remove')
|
|
81
|
+
.argument('<name>', 'the service to remove')
|
|
82
|
+
.option('-y, --yes', 'remove it without asking')
|
|
83
|
+
.description('remove a service with its volume and variables')
|
|
84
|
+
.action(commandAction((format, command, name) => removeService(name, command.opts().yes === true, format)));
|
package/dist/commands/status.js
CHANGED
|
@@ -11,5 +11,5 @@ async function status(format) {
|
|
|
11
11
|
printResult(format, project, () => `${color.heading(project.name)}\n\n${servicesTable(project)}`);
|
|
12
12
|
}
|
|
13
13
|
export const statusCommand = new Command('status')
|
|
14
|
-
.description(
|
|
14
|
+
.description("show each service's address and status")
|
|
15
15
|
.action(commandAction((format) => status(format)));
|
package/dist/commands/volume.js
CHANGED
|
@@ -66,22 +66,22 @@ export async function removeVolume(mountPath, serviceName, skipAsking, format) {
|
|
|
66
66
|
receipts: [`Removed volume ${mountPath} from ${service.name} and deleted its data`],
|
|
67
67
|
});
|
|
68
68
|
}
|
|
69
|
-
export const volumeCommand = new Command('volume').description('keep
|
|
69
|
+
export const volumeCommand = new Command('volume').description('keep folders across deploys with volumes');
|
|
70
70
|
volumeCommand
|
|
71
71
|
.command('list', { isDefault: true })
|
|
72
72
|
.addOption(serviceOption('which service to read'))
|
|
73
|
-
.description('list the
|
|
73
|
+
.description('list the volumes of a project')
|
|
74
74
|
.action(commandAction((format, command) => listVolumes(serviceNameOf(command), format)));
|
|
75
75
|
volumeCommand
|
|
76
76
|
.command('add')
|
|
77
77
|
.argument('<path>', 'the folder inside the service to keep, such as /app/data')
|
|
78
78
|
.addOption(serviceOption('which service keeps it'))
|
|
79
|
-
.description('
|
|
79
|
+
.description('add a volume to a service')
|
|
80
80
|
.action(commandAction((format, command, path) => addVolume(path, serviceNameOf(command), format)));
|
|
81
81
|
volumeCommand
|
|
82
82
|
.command('remove')
|
|
83
83
|
.argument('<path>', 'a folder the service keeps')
|
|
84
84
|
.addOption(serviceOption('which service keeps it'))
|
|
85
85
|
.option('-y, --yes', 'remove it without asking')
|
|
86
|
-
.description('
|
|
86
|
+
.description('delete a volume and its data')
|
|
87
87
|
.action(commandAction((format, command, path) => removeVolume(path, serviceNameOf(command), command.opts().yes === true, format)));
|
package/dist/commands/whoami.js
CHANGED
|
@@ -12,5 +12,5 @@ async function whoami(format) {
|
|
|
12
12
|
printResult(format, { ...profile, teams }, () => `Signed in as ${profile.email}\n\n${table(['TEAM', 'SLUG'], teams.map((team) => [team.name, team.slug]))}`);
|
|
13
13
|
}
|
|
14
14
|
export const whoamiCommand = new Command('whoami')
|
|
15
|
-
.description('show
|
|
15
|
+
.description('show which account this machine is signed in with')
|
|
16
16
|
.action(commandAction((format) => whoami(format)));
|
package/dist/index.js
CHANGED
|
@@ -1,60 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
grammar, and each command's own file holds its flags; here are only the flags they all share. */
|
|
4
|
-
import { Command } from 'commander';
|
|
5
|
-
import { accessCommand } from './commands/access.js';
|
|
6
|
-
import { deployCommand } from './commands/deploy/deploy.js';
|
|
7
|
-
import { envCommand } from './commands/env.js';
|
|
8
|
-
import { linkCommand, unlinkCommand } from './commands/link.js';
|
|
9
|
-
import { loginCommand } from './commands/login/login.js';
|
|
10
|
-
import { logoutCommand } from './commands/logout.js';
|
|
11
|
-
import { logsCommand } from './commands/logs.js';
|
|
12
|
-
import { openPathCommand } from './commands/open-path.js';
|
|
13
|
-
import { projectCommand } from './commands/project.js';
|
|
14
|
-
import { restartCommand } from './commands/restart.js';
|
|
15
|
-
import { serviceCommand } from './commands/service.js';
|
|
16
|
-
import { statusCommand } from './commands/status.js';
|
|
17
|
-
import { volumeCommand } from './commands/volume.js';
|
|
18
|
-
import { whoamiCommand } from './commands/whoami.js';
|
|
19
|
-
import { setJsonOutput } from './lib/prompt.js';
|
|
20
|
-
import { setNamedProject } from './lib/resolve-project.js';
|
|
21
|
-
import { setNamedTeam } from './lib/resolve-team.js';
|
|
22
|
-
import { routeUsageErrors } from './lib/usage-errors.js';
|
|
23
|
-
import { cliVersion } from './lib/version.js';
|
|
24
|
-
const program = new Command()
|
|
25
|
-
.name('grayspar')
|
|
26
|
-
.description('deploy your applications and share them with exactly the people you choose')
|
|
27
|
-
.version(cliVersion, '--version', 'show the version')
|
|
28
|
-
.option('--json', 'answer in JSON, for scripts and agents')
|
|
29
|
-
.option('--team <slug>', 'which of your teams to work in')
|
|
30
|
-
.option('--project <name>', 'which project to act on, instead of the linked one')
|
|
31
|
-
.option('--no-color', 'print without colour')
|
|
32
|
-
.hook('preAction', (_program, command) => {
|
|
33
|
-
const { json, team, project } = command.optsWithGlobals();
|
|
34
|
-
setJsonOutput(json === true);
|
|
35
|
-
setNamedTeam(team);
|
|
36
|
-
setNamedProject(project);
|
|
37
|
-
});
|
|
38
|
-
for (const command of [
|
|
39
|
-
loginCommand,
|
|
40
|
-
logoutCommand,
|
|
41
|
-
whoamiCommand,
|
|
42
|
-
deployCommand,
|
|
43
|
-
projectCommand,
|
|
44
|
-
accessCommand,
|
|
45
|
-
openPathCommand,
|
|
46
|
-
envCommand,
|
|
47
|
-
volumeCommand,
|
|
48
|
-
serviceCommand,
|
|
49
|
-
linkCommand,
|
|
50
|
-
unlinkCommand,
|
|
51
|
-
statusCommand,
|
|
52
|
-
logsCommand,
|
|
53
|
-
restartCommand,
|
|
54
|
-
]) {
|
|
55
|
-
program.addCommand(command);
|
|
56
|
-
}
|
|
57
|
-
routeUsageErrors(program);
|
|
2
|
+
import { program } from './program.js';
|
|
58
3
|
// A reader such as `head` can leave early, which ends the output and nothing more.
|
|
59
4
|
process.stdout.on('error', (error) => {
|
|
60
5
|
if (error.code === 'EPIPE')
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
/* A request that never arrived or failed without Grayspar's
|
|
2
|
-
for
|
|
3
|
-
count from the start of the first failing attempt, and each later attempt is cut off when they are up. */
|
|
1
|
+
/* A request that never arrived or failed without Grayspar's answer retries with growing, jittered waits while failures
|
|
2
|
+
run unbroken, for two minutes from the first failing attempt, which also end the last; then the failure stands. */
|
|
4
3
|
import { setTimeout as pause } from 'node:timers/promises';
|
|
5
4
|
import { CliError } from './errors.js';
|
|
6
5
|
const FIRST_DELAY_MS = 1000;
|
|
@@ -5,6 +5,8 @@ const RUNTIME_STATUS = {
|
|
|
5
5
|
running: { label: 'Running', tint: color.ok },
|
|
6
6
|
restarting: { label: 'Restarting', tint: untinted },
|
|
7
7
|
crashed: { label: 'Crashed', tint: color.bad },
|
|
8
|
+
sleeping: { label: 'Sleeping', tint: untinted },
|
|
9
|
+
stopped: { label: 'Stopped', tint: untinted },
|
|
8
10
|
unknown: { label: 'Unknown', tint: untinted },
|
|
9
11
|
failed: { label: 'Failed', tint: color.bad },
|
|
10
12
|
};
|
package/dist/program.js
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/* Every command is a singular noun and one of four verbs, or a single flat verb. `.claude/rules/cli.md` holds the
|
|
2
|
+
grammar, and each command's own file holds its flags; here are only the flags they all share. */
|
|
3
|
+
import { Command } from 'commander';
|
|
4
|
+
import { accessCommand } from './commands/access.js';
|
|
5
|
+
import { deployCommand } from './commands/deploy/deploy.js';
|
|
6
|
+
import { envCommand } from './commands/env.js';
|
|
7
|
+
import { linkCommand, unlinkCommand } from './commands/link.js';
|
|
8
|
+
import { loginCommand } from './commands/login/login.js';
|
|
9
|
+
import { logoutCommand } from './commands/logout.js';
|
|
10
|
+
import { logsCommand } from './commands/logs.js';
|
|
11
|
+
import { openPathCommand } from './commands/open-path.js';
|
|
12
|
+
import { projectCommand } from './commands/project.js';
|
|
13
|
+
import { restartCommand } from './commands/restart.js';
|
|
14
|
+
import { serviceCommand } from './commands/service.js';
|
|
15
|
+
import { statusCommand } from './commands/status.js';
|
|
16
|
+
import { volumeCommand } from './commands/volume.js';
|
|
17
|
+
import { whoamiCommand } from './commands/whoami.js';
|
|
18
|
+
import { setJsonOutput } from './lib/prompt.js';
|
|
19
|
+
import { setNamedProject } from './lib/resolve-project.js';
|
|
20
|
+
import { setNamedTeam } from './lib/resolve-team.js';
|
|
21
|
+
import { routeUsageErrors } from './lib/usage-errors.js';
|
|
22
|
+
import { cliVersion } from './lib/version.js';
|
|
23
|
+
export const program = new Command()
|
|
24
|
+
.name('grayspar')
|
|
25
|
+
.description('deploy your applications and share them with exactly the people you choose')
|
|
26
|
+
.version(cliVersion, '--version', 'show the version')
|
|
27
|
+
.option('--json', 'answer in JSON, for scripts and agents')
|
|
28
|
+
.option('--team <slug>', 'which of your teams to work in')
|
|
29
|
+
.option('--project <name>', 'which project to act on, instead of the linked one')
|
|
30
|
+
.option('--no-color', 'print without colour')
|
|
31
|
+
.hook('preAction', (_program, command) => {
|
|
32
|
+
const { json, team, project } = command.optsWithGlobals();
|
|
33
|
+
setJsonOutput(json === true);
|
|
34
|
+
setNamedTeam(team);
|
|
35
|
+
setNamedProject(project);
|
|
36
|
+
});
|
|
37
|
+
for (const command of [
|
|
38
|
+
loginCommand,
|
|
39
|
+
logoutCommand,
|
|
40
|
+
whoamiCommand,
|
|
41
|
+
deployCommand,
|
|
42
|
+
projectCommand,
|
|
43
|
+
accessCommand,
|
|
44
|
+
openPathCommand,
|
|
45
|
+
envCommand,
|
|
46
|
+
volumeCommand,
|
|
47
|
+
serviceCommand,
|
|
48
|
+
linkCommand,
|
|
49
|
+
unlinkCommand,
|
|
50
|
+
statusCommand,
|
|
51
|
+
logsCommand,
|
|
52
|
+
restartCommand,
|
|
53
|
+
]) {
|
|
54
|
+
program.addCommand(command);
|
|
55
|
+
}
|
|
56
|
+
routeUsageErrors(program);
|
package/package.json
CHANGED