@procedures/tailscale 0.33.0 → 0.35.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 +51 -0
- package/dist/.tsbuildinfo +1 -1
- package/dist/argv.d.ts +3 -17
- package/dist/argv.d.ts.map +1 -1
- package/dist/argv.js +3 -45
- package/dist/argv.js.map +1 -1
- package/dist/module.d.ts +2 -1
- package/dist/module.d.ts.map +1 -1
- package/dist/module.js +5 -3
- package/dist/module.js.map +1 -1
- package/dist/procedures.d.ts +4 -0
- package/dist/procedures.d.ts.map +1 -1
- package/dist/procedures.js +20 -7
- package/dist/procedures.js.map +1 -1
- package/package.json +3 -3
- package/src/argv.ts +3 -44
- package/src/module.ts +5 -3
- package/src/procedures.ts +22 -8
package/dist/argv.d.ts
CHANGED
|
@@ -1,21 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The argv of a CLI command from a command path, named arguments and positional arguments. The
|
|
3
|
-
* procedures start the program with argv, never through a shell.
|
|
3
|
+
* procedures start the program with argv, never through a shell. The helpers are the shared ones
|
|
4
|
+
* of `@procedures/shell/cli` (dedupe review F2): this file keeps the exports of this package.
|
|
4
5
|
*/
|
|
5
|
-
export
|
|
6
|
-
/** A flag name: lowercase letters, digits and hyphens. */
|
|
7
|
-
export declare const FLAG_NAME: RegExp;
|
|
8
|
-
/**
|
|
9
|
-
* This function gives the argv: the path, then each named argument, then `--` and the positional
|
|
10
|
-
* arguments. `true` gives `--key`, `false` gives nothing, a list gives `--key=value` for each
|
|
11
|
-
* value, and another value gives `--key=value`.
|
|
12
|
-
*/
|
|
13
|
-
export declare function buildArgv(input: {
|
|
14
|
-
readonly path: readonly string[];
|
|
15
|
-
readonly args?: Readonly<Record<string, ArgValue>> | undefined;
|
|
16
|
-
readonly positional?: readonly string[] | undefined;
|
|
17
|
-
readonly flags?: readonly string[] | undefined;
|
|
18
|
-
}): string[];
|
|
19
|
-
/** This function reads JSON output: one document, or one document on each line (NDJSON). */
|
|
20
|
-
export declare function parseJsonOutput(stdout: string): unknown;
|
|
6
|
+
export { buildArgv, parseJsonOutput, FLAG_NAME, type ArgValue } from "@procedures/shell/cli";
|
|
21
7
|
//# sourceMappingURL=argv.d.ts.map
|
package/dist/argv.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"argv.d.ts","sourceRoot":"","sources":["../src/argv.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"argv.d.ts","sourceRoot":"","sources":["../src/argv.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,SAAS,EAAE,eAAe,EAAE,SAAS,EAAE,KAAK,QAAQ,EAAE,MAAM,uBAAuB,CAAC"}
|
package/dist/argv.js
CHANGED
|
@@ -1,49 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The argv of a CLI command from a command path, named arguments and positional arguments. The
|
|
3
|
-
* procedures start the program with argv, never through a shell.
|
|
3
|
+
* procedures start the program with argv, never through a shell. The helpers are the shared ones
|
|
4
|
+
* of `@procedures/shell/cli` (dedupe review F2): this file keeps the exports of this package.
|
|
4
5
|
*/
|
|
5
|
-
|
|
6
|
-
export const FLAG_NAME = /^[a-z][a-z0-9-]*$/;
|
|
7
|
-
/**
|
|
8
|
-
* This function gives the argv: the path, then each named argument, then `--` and the positional
|
|
9
|
-
* arguments. `true` gives `--key`, `false` gives nothing, a list gives `--key=value` for each
|
|
10
|
-
* value, and another value gives `--key=value`.
|
|
11
|
-
*/
|
|
12
|
-
export function buildArgv(input) {
|
|
13
|
-
const argv = [...input.path, ...(input.flags ?? [])];
|
|
14
|
-
for (const [key, value] of Object.entries(input.args ?? {})) {
|
|
15
|
-
if (!FLAG_NAME.test(key))
|
|
16
|
-
throw new Error(`The argument name "${key}" is not a flag name (lowercase letters, digits, hyphens)`);
|
|
17
|
-
if (value === false)
|
|
18
|
-
continue;
|
|
19
|
-
if (value === true)
|
|
20
|
-
argv.push(`--${key}`);
|
|
21
|
-
else if (Array.isArray(value))
|
|
22
|
-
for (const item of value)
|
|
23
|
-
argv.push(`--${key}=${item}`);
|
|
24
|
-
else
|
|
25
|
-
argv.push(`--${key}=${String(value)}`);
|
|
26
|
-
}
|
|
27
|
-
if (input.positional && input.positional.length > 0)
|
|
28
|
-
argv.push("--", ...input.positional);
|
|
29
|
-
return argv;
|
|
30
|
-
}
|
|
31
|
-
/** This function reads JSON output: one document, or one document on each line (NDJSON). */
|
|
32
|
-
export function parseJsonOutput(stdout) {
|
|
33
|
-
const text = stdout.trim();
|
|
34
|
-
if (text.length === 0)
|
|
35
|
-
return undefined;
|
|
36
|
-
try {
|
|
37
|
-
return JSON.parse(text);
|
|
38
|
-
}
|
|
39
|
-
catch {
|
|
40
|
-
const lines = text.split(/\r?\n/).filter((line) => line.trim().length > 0);
|
|
41
|
-
try {
|
|
42
|
-
return lines.map((line) => JSON.parse(line));
|
|
43
|
-
}
|
|
44
|
-
catch {
|
|
45
|
-
return undefined;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
}
|
|
6
|
+
export { buildArgv, parseJsonOutput, FLAG_NAME } from "@procedures/shell/cli";
|
|
49
7
|
//# sourceMappingURL=argv.js.map
|
package/dist/argv.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"argv.js","sourceRoot":"","sources":["../src/argv.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"argv.js","sourceRoot":"","sources":["../src/argv.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,SAAS,EAAE,eAAe,EAAE,SAAS,EAAiB,MAAM,uBAAuB,CAAC"}
|
package/dist/module.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The procedures of @procedures/tailscale as data. Importing this file registers nothing.
|
|
2
|
+
* The procedures of @procedures/tailscale as data. Importing this file registers nothing. The
|
|
3
|
+
* module requires the module of `@procedures/shell`: `tailscale.run` calls `shell.run`.
|
|
3
4
|
*/
|
|
4
5
|
import { type ProcedureModule, type ProcedureRegistry } from "@procedures/client";
|
|
5
6
|
import { type TailscaleProceduresOptions } from "./procedures.js";
|
package/dist/module.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../src/module.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../src/module.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAqD,KAAK,eAAe,EAAE,KAAK,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAErI,OAAO,EAAkD,KAAK,0BAA0B,EAAE,MAAM,iBAAiB,CAAC;AAElH,eAAO,MAAM,eAAe,EAAE,eAA2F,CAAC;eAE3G,eAAe;AAE9B;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,GAAE,0BAA+B,GAAG,eAAe,CAG/F;AAED;;;GAGG;AACH,wBAAgB,2BAA2B,CAAC,QAAQ,GAAE,iBAAsC,GAAG,IAAI,CAElG"}
|
package/dist/module.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The procedures of @procedures/tailscale as data. Importing this file registers nothing.
|
|
2
|
+
* The procedures of @procedures/tailscale as data. Importing this file registers nothing. The
|
|
3
|
+
* module requires the module of `@procedures/shell`: `tailscale.run` calls `shell.run`.
|
|
3
4
|
*/
|
|
4
5
|
import { defineModule, registerModules, PROCEDURE_REGISTRY } from "@procedures/client";
|
|
6
|
+
import shellModule from "@procedures/shell/module";
|
|
5
7
|
import { createTailscaleProcedures, tailscaleProcedures } from "./procedures.js";
|
|
6
|
-
export const tailscaleModule = defineModule("@procedures/tailscale", tailscaleProcedures);
|
|
8
|
+
export const tailscaleModule = defineModule("@procedures/tailscale", tailscaleProcedures, [shellModule]);
|
|
7
9
|
export default tailscaleModule;
|
|
8
10
|
/**
|
|
9
11
|
* This function gives the module with the settings of the host: the tailscale program. Without
|
|
@@ -16,7 +18,7 @@ export default tailscaleModule;
|
|
|
16
18
|
export function createTailscaleModule(options = {}) {
|
|
17
19
|
if (options.executable === undefined)
|
|
18
20
|
return tailscaleModule;
|
|
19
|
-
return defineModule("@procedures/tailscale", createTailscaleProcedures(options));
|
|
21
|
+
return defineModule("@procedures/tailscale", createTailscaleProcedures(options), [shellModule]);
|
|
20
22
|
}
|
|
21
23
|
/**
|
|
22
24
|
* This function registers the procedures of @procedures/tailscale in a registry. The default is the
|
package/dist/module.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"module.js","sourceRoot":"","sources":["../src/module.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"module.js","sourceRoot":"","sources":["../src/module.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,kBAAkB,EAAgD,MAAM,oBAAoB,CAAC;AACrI,OAAO,WAAW,MAAM,0BAA0B,CAAC;AACnD,OAAO,EAAE,yBAAyB,EAAE,mBAAmB,EAAmC,MAAM,iBAAiB,CAAC;AAElH,MAAM,CAAC,MAAM,eAAe,GAAoB,YAAY,CAAC,uBAAuB,EAAE,mBAAmB,EAAE,CAAC,WAAW,CAAC,CAAC,CAAC;AAE1H,eAAe,eAAe,CAAC;AAE/B;;;;;;;GAOG;AACH,MAAM,UAAU,qBAAqB,CAAC,OAAO,GAA+B,EAAE;IAC5E,IAAI,OAAO,CAAC,UAAU,KAAK,SAAS;QAAE,OAAO,eAAe,CAAC;IAC7D,OAAO,YAAY,CAAC,uBAAuB,EAAE,yBAAyB,CAAC,OAAO,CAAC,EAAE,CAAC,WAAW,CAAC,CAAC,CAAC;AAClG,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,2BAA2B,CAAC,QAAQ,GAAsB,kBAAkB;IAC1F,eAAe,CAAC,QAAQ,EAAE,CAAC,eAAe,CAAC,CAAC,CAAC;AAC/C,CAAC"}
|
package/dist/procedures.d.ts
CHANGED
|
@@ -5,6 +5,10 @@
|
|
|
5
5
|
* The program comes from the host, never from the input (audit pass 2, O55): the `executable`
|
|
6
6
|
* option of `createTailscaleModule()`, then `TAILSCALE_EXE`, then the install folder, then
|
|
7
7
|
* `tailscale`. Before, an input `executable` let the caller start any program.
|
|
8
|
+
*
|
|
9
|
+
* The procedure starts tailscale with argv through a nested `shell.run` call, the runner of
|
|
10
|
+
* `defineCliTool()` (tracker item O64). The engine applies its rules to that call, and a cancel
|
|
11
|
+
* stops the program.
|
|
8
12
|
*/
|
|
9
13
|
import { type AnyProcedure } from "@procedures/client";
|
|
10
14
|
/** This function gives the tailscale program: the explicit value, TAILSCALE_EXE, the Windows install folder, then tailscale. */
|
package/dist/procedures.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"procedures.d.ts","sourceRoot":"","sources":["../src/procedures.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"procedures.d.ts","sourceRoot":"","sources":["../src/procedures.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,EAAmB,KAAK,YAAY,EAAyB,MAAM,oBAAoB,CAAC;AAQ/F,gIAAgI;AAChI,wBAAgB,mBAAmB,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAK7D;AA+CD,2GAA2G;AAC3G,MAAM,WAAW,0BAA0B;IACzC,gHAAgH;IAChH,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC1C;AAED;;;GAGG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,GAAE,0BAA+B,GAAG,YAAY,EAAE,CAsClG;AAED,eAAO,MAAM,mBAAmB,EAAE,YAAY,EAAgC,CAAC;AAE/E,eAAO,MAAM,qBAAqB,EAAE,YAAsC,CAAC"}
|
package/dist/procedures.js
CHANGED
|
@@ -5,10 +5,14 @@
|
|
|
5
5
|
* The program comes from the host, never from the input (audit pass 2, O55): the `executable`
|
|
6
6
|
* option of `createTailscaleModule()`, then `TAILSCALE_EXE`, then the install folder, then
|
|
7
7
|
* `tailscale`. Before, an input `executable` let the caller start any program.
|
|
8
|
+
*
|
|
9
|
+
* The procedure starts tailscale with argv through a nested `shell.run` call, the runner of
|
|
10
|
+
* `defineCliTool()` (tracker item O64). The engine applies its rules to that call, and a cancel
|
|
11
|
+
* stops the program.
|
|
8
12
|
*/
|
|
9
13
|
import { existsSync } from "node:fs";
|
|
10
|
-
import { createProcedure
|
|
11
|
-
import {
|
|
14
|
+
import { createProcedure } from "@procedures/client";
|
|
15
|
+
import { defineCliTool } from "@procedures/shell/cli";
|
|
12
16
|
import { z } from "zod";
|
|
13
17
|
import { buildArgv, parseJsonOutput } from "./argv.js";
|
|
14
18
|
/** The Windows installer does not put tailscale on the PATH. */
|
|
@@ -47,20 +51,29 @@ const RunOutputSchema = z.object({
|
|
|
47
51
|
json: z.unknown().optional(),
|
|
48
52
|
actionUrl: z.string().optional(),
|
|
49
53
|
});
|
|
54
|
+
/**
|
|
55
|
+
* True when the output of `shell.run` tells that the program did not start. `shell.run` gives
|
|
56
|
+
* exit code 1 and the error of the start as the standard error, for example `spawn tailscale ENOENT`.
|
|
57
|
+
*/
|
|
58
|
+
function didNotStart(result) {
|
|
59
|
+
return result.exitCode === 1 && result.stdout === "" && /^spawn .+ E[A-Z0-9]+$/.test(result.stderr);
|
|
60
|
+
}
|
|
50
61
|
/**
|
|
51
62
|
* This function gives the procedures of this package with the settings of the host. The program
|
|
52
63
|
* of `tailscale.run` is `executable`, or the default of `tailscaleExecutable()` at each call.
|
|
53
64
|
*/
|
|
54
65
|
export function createTailscaleProcedures(options = {}) {
|
|
66
|
+
const tailscale = defineCliTool({ name: "tailscale", resolve: () => tailscaleExecutable(options.executable) });
|
|
55
67
|
const run = createProcedure()
|
|
56
68
|
.path(["tailscale", "run"])
|
|
57
|
-
.input(
|
|
58
|
-
.output(
|
|
69
|
+
.input(RunInputSchema)
|
|
70
|
+
.output(RunOutputSchema)
|
|
59
71
|
.meta({
|
|
60
72
|
description: "Run a tailscale command (status, ip, serve, up, down)",
|
|
61
73
|
args: ["path"],
|
|
62
74
|
shorts: { timeout: "t" },
|
|
63
75
|
output: "json",
|
|
76
|
+
calls: ["shell.run"],
|
|
64
77
|
// up, down and serve change the network of the host. ssh starts a program on another host.
|
|
65
78
|
effects: { reads: ["network"], writes: ["network", "process"], destructive: true },
|
|
66
79
|
})
|
|
@@ -71,9 +84,9 @@ export function createTailscaleProcedures(options = {}) {
|
|
|
71
84
|
args: input.args,
|
|
72
85
|
positional: input.positional,
|
|
73
86
|
});
|
|
74
|
-
const result = await
|
|
75
|
-
if (result
|
|
76
|
-
throw new Error(`tailscale did not start: ${result.
|
|
87
|
+
const result = await tailscale.call(argv, { timeout: input.timeout }, ctx);
|
|
88
|
+
if (didNotStart(result))
|
|
89
|
+
throw new Error(`tailscale did not start: ${result.stderr}`);
|
|
77
90
|
const json = input.json ? parseJsonOutput(result.stdout) : undefined;
|
|
78
91
|
// tailscale prints an address to open when a login or a feature needs the owner
|
|
79
92
|
const actionUrl = /https:\/\/login\.tailscale\.com\/\S+/.exec(`${result.stdout}\n${result.stderr}`)?.[0];
|
package/dist/procedures.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"procedures.js","sourceRoot":"","sources":["../src/procedures.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"procedures.js","sourceRoot":"","sources":["../src/procedures.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/package",
|
|
3
3
|
"name": "@procedures/tailscale",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.35.0",
|
|
5
5
|
"description": "The tailscale CLI as one procedure: status, ip, serve, up and down, with JSON output.",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"tailscale",
|
|
@@ -56,9 +56,9 @@
|
|
|
56
56
|
"provenance": true
|
|
57
57
|
},
|
|
58
58
|
"dependencies": {
|
|
59
|
-
"@procedures/client": "0.
|
|
59
|
+
"@procedures/client": "0.35.0",
|
|
60
60
|
"zod": "^4.6.5",
|
|
61
|
-
"@procedures/shell": "0.
|
|
61
|
+
"@procedures/shell": "0.35.0"
|
|
62
62
|
},
|
|
63
63
|
"devDependencies": {
|
|
64
64
|
"typescript": "^7.0.2",
|
package/src/argv.ts
CHANGED
|
@@ -1,48 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The argv of a CLI command from a command path, named arguments and positional arguments. The
|
|
3
|
-
* procedures start the program with argv, never through a shell.
|
|
3
|
+
* procedures start the program with argv, never through a shell. The helpers are the shared ones
|
|
4
|
+
* of `@procedures/shell/cli` (dedupe review F2): this file keeps the exports of this package.
|
|
4
5
|
*/
|
|
5
6
|
|
|
6
|
-
export
|
|
7
|
-
|
|
8
|
-
/** A flag name: lowercase letters, digits and hyphens. */
|
|
9
|
-
export const FLAG_NAME: RegExp = /^[a-z][a-z0-9-]*$/;
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* This function gives the argv: the path, then each named argument, then `--` and the positional
|
|
13
|
-
* arguments. `true` gives `--key`, `false` gives nothing, a list gives `--key=value` for each
|
|
14
|
-
* value, and another value gives `--key=value`.
|
|
15
|
-
*/
|
|
16
|
-
export function buildArgv(input: {
|
|
17
|
-
readonly path: readonly string[];
|
|
18
|
-
readonly args?: Readonly<Record<string, ArgValue>> | undefined;
|
|
19
|
-
readonly positional?: readonly string[] | undefined;
|
|
20
|
-
readonly flags?: readonly string[] | undefined;
|
|
21
|
-
}): string[] {
|
|
22
|
-
const argv = [...input.path, ...(input.flags ?? [])];
|
|
23
|
-
for (const [key, value] of Object.entries(input.args ?? {})) {
|
|
24
|
-
if (!FLAG_NAME.test(key)) throw new Error(`The argument name "${key}" is not a flag name (lowercase letters, digits, hyphens)`);
|
|
25
|
-
if (value === false) continue;
|
|
26
|
-
if (value === true) argv.push(`--${key}`);
|
|
27
|
-
else if (Array.isArray(value)) for (const item of value) argv.push(`--${key}=${item}`);
|
|
28
|
-
else argv.push(`--${key}=${String(value)}`);
|
|
29
|
-
}
|
|
30
|
-
if (input.positional && input.positional.length > 0) argv.push("--", ...input.positional);
|
|
31
|
-
return argv;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/** This function reads JSON output: one document, or one document on each line (NDJSON). */
|
|
35
|
-
export function parseJsonOutput(stdout: string): unknown {
|
|
36
|
-
const text = stdout.trim();
|
|
37
|
-
if (text.length === 0) return undefined;
|
|
38
|
-
try {
|
|
39
|
-
return JSON.parse(text);
|
|
40
|
-
} catch {
|
|
41
|
-
const lines = text.split(/\r?\n/).filter((line) => line.trim().length > 0);
|
|
42
|
-
try {
|
|
43
|
-
return lines.map((line) => JSON.parse(line) as unknown);
|
|
44
|
-
} catch {
|
|
45
|
-
return undefined;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
}
|
|
7
|
+
export { buildArgv, parseJsonOutput, FLAG_NAME, type ArgValue } from "@procedures/shell/cli";
|
package/src/module.ts
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The procedures of @procedures/tailscale as data. Importing this file registers nothing.
|
|
2
|
+
* The procedures of @procedures/tailscale as data. Importing this file registers nothing. The
|
|
3
|
+
* module requires the module of `@procedures/shell`: `tailscale.run` calls `shell.run`.
|
|
3
4
|
*/
|
|
4
5
|
|
|
5
6
|
import { defineModule, registerModules, PROCEDURE_REGISTRY, type ProcedureModule, type ProcedureRegistry } from "@procedures/client";
|
|
7
|
+
import shellModule from "@procedures/shell/module";
|
|
6
8
|
import { createTailscaleProcedures, tailscaleProcedures, type TailscaleProceduresOptions } from "./procedures.js";
|
|
7
9
|
|
|
8
|
-
export const tailscaleModule: ProcedureModule = defineModule("@procedures/tailscale", tailscaleProcedures);
|
|
10
|
+
export const tailscaleModule: ProcedureModule = defineModule("@procedures/tailscale", tailscaleProcedures, [shellModule]);
|
|
9
11
|
|
|
10
12
|
export default tailscaleModule;
|
|
11
13
|
|
|
@@ -19,7 +21,7 @@ export default tailscaleModule;
|
|
|
19
21
|
*/
|
|
20
22
|
export function createTailscaleModule(options: TailscaleProceduresOptions = {}): ProcedureModule {
|
|
21
23
|
if (options.executable === undefined) return tailscaleModule;
|
|
22
|
-
return defineModule("@procedures/tailscale", createTailscaleProcedures(options));
|
|
24
|
+
return defineModule("@procedures/tailscale", createTailscaleProcedures(options), [shellModule]);
|
|
23
25
|
}
|
|
24
26
|
|
|
25
27
|
/**
|
package/src/procedures.ts
CHANGED
|
@@ -5,11 +5,15 @@
|
|
|
5
5
|
* The program comes from the host, never from the input (audit pass 2, O55): the `executable`
|
|
6
6
|
* option of `createTailscaleModule()`, then `TAILSCALE_EXE`, then the install folder, then
|
|
7
7
|
* `tailscale`. Before, an input `executable` let the caller start any program.
|
|
8
|
+
*
|
|
9
|
+
* The procedure starts tailscale with argv through a nested `shell.run` call, the runner of
|
|
10
|
+
* `defineCliTool()` (tracker item O64). The engine applies its rules to that call, and a cancel
|
|
11
|
+
* stops the program.
|
|
8
12
|
*/
|
|
9
13
|
|
|
10
14
|
import { existsSync } from "node:fs";
|
|
11
|
-
import { createProcedure,
|
|
12
|
-
import {
|
|
15
|
+
import { createProcedure, type AnyProcedure, type ProcedureContext } from "@procedures/client";
|
|
16
|
+
import { defineCliTool } from "@procedures/shell/cli";
|
|
13
17
|
import { z } from "zod";
|
|
14
18
|
import { buildArgv, parseJsonOutput, type ArgValue } from "./argv.js";
|
|
15
19
|
|
|
@@ -61,6 +65,14 @@ const RunOutputSchema = z.object({
|
|
|
61
65
|
actionUrl: z.string().optional(),
|
|
62
66
|
});
|
|
63
67
|
|
|
68
|
+
/**
|
|
69
|
+
* True when the output of `shell.run` tells that the program did not start. `shell.run` gives
|
|
70
|
+
* exit code 1 and the error of the start as the standard error, for example `spawn tailscale ENOENT`.
|
|
71
|
+
*/
|
|
72
|
+
function didNotStart(result: { exitCode: number; stdout: string; stderr: string }): boolean {
|
|
73
|
+
return result.exitCode === 1 && result.stdout === "" && /^spawn .+ E[A-Z0-9]+$/.test(result.stderr);
|
|
74
|
+
}
|
|
75
|
+
|
|
64
76
|
/** The settings of the host for the procedures of this package. The procedure input cannot change them. */
|
|
65
77
|
export interface TailscaleProceduresOptions {
|
|
66
78
|
/** The tailscale program. The default is `TAILSCALE_EXE`, then the Windows install folder, then `tailscale`. */
|
|
@@ -72,27 +84,29 @@ export interface TailscaleProceduresOptions {
|
|
|
72
84
|
* of `tailscale.run` is `executable`, or the default of `tailscaleExecutable()` at each call.
|
|
73
85
|
*/
|
|
74
86
|
export function createTailscaleProcedures(options: TailscaleProceduresOptions = {}): AnyProcedure[] {
|
|
87
|
+
const tailscale = defineCliTool({ name: "tailscale", resolve: () => tailscaleExecutable(options.executable) });
|
|
75
88
|
const run = createProcedure()
|
|
76
89
|
.path(["tailscale", "run"])
|
|
77
|
-
.input(
|
|
78
|
-
.output(
|
|
90
|
+
.input(RunInputSchema)
|
|
91
|
+
.output(RunOutputSchema)
|
|
79
92
|
.meta({
|
|
80
93
|
description: "Run a tailscale command (status, ip, serve, up, down)",
|
|
81
94
|
args: ["path"],
|
|
82
95
|
shorts: { timeout: "t" },
|
|
83
96
|
output: "json",
|
|
97
|
+
calls: ["shell.run"],
|
|
84
98
|
// up, down and serve change the network of the host. ssh starts a program on another host.
|
|
85
99
|
effects: { reads: ["network"], writes: ["network", "process"], destructive: true },
|
|
86
100
|
})
|
|
87
|
-
.handler(async (input: RunInput, ctx
|
|
101
|
+
.handler(async (input: RunInput, ctx: ProcedureContext): Promise<RunOutput> => {
|
|
88
102
|
const argv = buildArgv({
|
|
89
103
|
path: input.path,
|
|
90
104
|
flags: input.json ? ["--json"] : [],
|
|
91
105
|
args: input.args as Record<string, ArgValue> | undefined,
|
|
92
106
|
positional: input.positional,
|
|
93
107
|
});
|
|
94
|
-
const result = await
|
|
95
|
-
if (result
|
|
108
|
+
const result = await tailscale.call(argv, { timeout: input.timeout }, ctx);
|
|
109
|
+
if (didNotStart(result)) throw new Error(`tailscale did not start: ${result.stderr}`);
|
|
96
110
|
const json = input.json ? parseJsonOutput(result.stdout) : undefined;
|
|
97
111
|
// tailscale prints an address to open when a login or a feature needs the owner
|
|
98
112
|
const actionUrl = /https:\/\/login\.tailscale\.com\/\S+/.exec(`${result.stdout}\n${result.stderr}`)?.[0];
|
|
@@ -105,7 +119,7 @@ export function createTailscaleProcedures(options: TailscaleProceduresOptions =
|
|
|
105
119
|
...(actionUrl !== undefined ? { actionUrl } : {}),
|
|
106
120
|
};
|
|
107
121
|
})
|
|
108
|
-
.build()
|
|
122
|
+
.build();
|
|
109
123
|
return [run];
|
|
110
124
|
}
|
|
111
125
|
|