Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions extensions/composite/tsh-ssh/extension.build.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"id": "tsh-ssh",
"kind": "composite",
"language": "static",
"path": "extensions/composite/tsh-ssh",
"targets": [
"universal"
],
"releaseTagPrefix": "tsh-ssh-v",
"r2Prefix": "extensions/tsh-ssh",
"published": false
}
64 changes: 64 additions & 0 deletions extensions/composite/tsh-ssh/extension.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
{
"schema_version": 1,
"id": "com.navop.tsh-ssh",
"name": "Teleport SSH (tsh)",
"version": "0.1.0",
"publisher": "Navop",
"license": "Apache-2.0",
"description": "Static composite extension that opens a Teleport server terminal by running `tsh ssh <target>` as a local process. The saved connection only stores the target (user@server), and no provider process is needed. 静态复合扩展,通过 Teleport 的 tsh ssh 打开服务器终端:连接只保存目标(user@server),由宿主以本地进程运行 tsh ssh,无需 provider 进程。",
"categories": ["developer-tools"],
"keywords": ["teleport", "tsh", "ssh", "terminal"],
"engines": {
"onetcli": ">=0.20.4"
},
"api": {
"extension": "1.0"
},
"contributes": {
"connections": [
{
"id": "tsh-ssh",
"label": "Teleport SSH (tsh)",
"description": "Open a shell on a Teleport-protected server with `tsh ssh`.",
"terminal": {
"command": "tsh",
"args": ["ssh", "{{target}}"],
"env": {
"TELEPORT_PROXY": "{{proxy}}",
"TELEPORT_CLUSTER": "{{cluster}}"
},
"title": "{{target}}"
},
"form": {
"tabs": [
{
"id": "general",
"label": "General",
"fields": [
{
"id": "target",
"label": "Target",
"fieldType": "Text",
"required": true,
"placeholder": "[user@]server.example.com"
},
{
"id": "proxy",
"label": "Proxy",
"fieldType": "Text",
"placeholder": "Optional, sets TELEPORT_PROXY"
},
{
"id": "cluster",
"label": "Cluster",
"fieldType": "Text",
"placeholder": "Optional, sets TELEPORT_CLUSTER"
}
]
}
]
}
}
]
}
}
21 changes: 19 additions & 2 deletions scripts/verify-composite-package.sh
Original file line number Diff line number Diff line change
Expand Up @@ -130,18 +130,35 @@ if (shellViews.length > 0) {
const connections = manifest.contributes?.connections || [];
const connectionIds = new Set();
for (const connection of connections) {
for (const key of ["id", "label", "runtimeId", "resourceType", "form"]) {
// 终端型连接(声明 terminal)由宿主直接起本地进程:没有 provider,
// 因此不得声明 runtimeId / resourceType / shellViewId。
const isTerminal = connection.terminal !== undefined;
const requiredKeys = isTerminal
? ["id", "label", "form"]
: ["id", "label", "runtimeId", "resourceType", "form"];
for (const key of requiredKeys) {
if (!connection[key]) {
console.error(`connection contribution missing ${key}`);
process.exit(1);
}
}
if (isTerminal) {
if (connection.runtimeId || connection.resourceType || connection.shellViewId) {
console.error(`terminal connection ${connection.id} must not declare runtimeId, resourceType or shellViewId`);
process.exit(1);
}
const command = connection.terminal.command;
if (typeof command !== "string" || command.trim() === "" || command.includes("{{")) {
console.error(`terminal connection ${connection.id} command must be a fixed non-empty string`);
process.exit(1);
}
}
if (connectionIds.has(connection.id)) {
console.error(`duplicate connection contribution id: ${connection.id}`);
process.exit(1);
}
connectionIds.add(connection.id);
if (!ipcIds.has(connection.runtimeId)) {
if (!isTerminal && !ipcIds.has(connection.runtimeId)) {
console.error(`connection ${connection.id} references unknown IPC runtime: ${connection.runtimeId}`);
process.exit(1);
}
Expand Down
39 changes: 39 additions & 0 deletions tests/scripts.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2051,6 +2051,45 @@ test("package-composite-extension creates a static remote editor package", () =>
);
});

test("package-composite-extension packages the provider-less tsh-ssh terminal connection", () => {
const workdir = makeTempDir();
copyScript("package-composite-extension.sh", workdir);
copyScript("verify-composite-package.sh", workdir);
fs.cpSync(
path.join(repoRoot, "extensions/composite/tsh-ssh"),
path.join(workdir, "extensions/composite/tsh-ssh"),
{ recursive: true },
);

const archivePath = execFileSync(
"bash",
[
path.join(workdir, "scripts/package-composite-extension.sh"),
"tsh-ssh",
"universal",
path.join(workdir, "artifacts"),
"1.2.3",
],
{ cwd: workdir, encoding: "utf8" },
).trim();

execFileSync("tar", ["xzf", archivePath, "-C", path.join(workdir, "unpacked")]);
const manifest = JSON.parse(
fs.readFileSync(path.join(workdir, "unpacked/extension.json"), "utf8"),
);
const connection = manifest.contributes.connections[0];
assert.equal(manifest.version, "1.2.3");
assert.equal(manifest.runtime, undefined);
assert.equal(connection.terminal.command, "tsh");
assert.equal(connection.runtimeId, undefined);
assert.equal(fs.existsSync(path.join(workdir, "unpacked/bin")), false);
execFileSync(
"bash",
[path.join(workdir, "scripts/verify-composite-package.sh"), archivePath],
{ cwd: workdir, encoding: "utf8" },
);
});

test("package-composite-extension creates a native shell provider package", () => {
const workdir = makeTempDir();
createNativeCompositeFixture(workdir);
Expand Down