Skip to content

Commit 26e14a2

Browse files
committed
fix: Use --template flag for 1Password CLI compatibility
The updated 1Password CLI no longer reads item templates from stdin via spawn. Switch to writing a temp file and passing it with --template, and pass metadata (title, vault, category) as explicit CLI flags.
1 parent c1843ef commit 26e14a2

1 file changed

Lines changed: 67 additions & 24 deletions

File tree

src/core/onepassword.ts

Lines changed: 67 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1+
import { unlinkSync, writeFileSync } from "node:fs";
2+
import { tmpdir } from "node:os";
3+
import { join } from "node:path";
14
import { errors } from "../utils/errors";
2-
import { exec, execWithStdin } from "../utils/shell";
5+
import { exec } from "../utils/shell";
36
import type { CreateItemOptions, CreateItemResult, EditItemOptions } from "./types";
47

58
interface VerboseOption {
@@ -82,10 +85,23 @@ interface OpItemResult {
8285
fields?: Array<{ label: string; id: string; type?: string }>;
8386
}
8487

85-
interface OpItemTemplate {
86-
title: string;
87-
vault: { name: string };
88-
category: string;
88+
let tempCounter = 0;
89+
90+
function writeTempTemplate(template: OpFieldsTemplate): string {
91+
const filePath = join(tmpdir(), `env2op-template-${process.pid}-${++tempCounter}.json`);
92+
writeFileSync(filePath, JSON.stringify(template), "utf-8");
93+
return filePath;
94+
}
95+
96+
function cleanupTempFile(filePath: string): void {
97+
try {
98+
unlinkSync(filePath);
99+
} catch {
100+
// ignore cleanup errors
101+
}
102+
}
103+
104+
interface OpFieldsTemplate {
89105
fields: Array<{
90106
type: "STRING" | "CONCEALED";
91107
label: string;
@@ -94,19 +110,12 @@ interface OpItemTemplate {
94110
}
95111

96112
/**
97-
* Build JSON template for 1Password item (works for both create and edit)
113+
* Build JSON template containing only fields for 1Password item.
114+
* Metadata (title, vault, category) is passed via CLI flags.
98115
*/
99-
function buildItemTemplate(
100-
title: string,
101-
vault: string,
102-
fields: Array<{ key: string; value: string }>,
103-
secret: boolean,
104-
): OpItemTemplate {
116+
function buildFieldsTemplate(fields: Array<{ key: string; value: string }>, secret: boolean): OpFieldsTemplate {
105117
const fieldType = secret ? "CONCEALED" : "STRING";
106118
return {
107-
title,
108-
vault: { name: vault },
109-
category: "SECURE_NOTE",
110119
fields: fields.map(({ key, value }) => ({
111120
type: fieldType,
112121
label: key,
@@ -121,11 +130,28 @@ function buildItemTemplate(
121130
export async function createSecureNote(options: CreateItemOptions & VerboseOption): Promise<CreateItemResult> {
122131
const { vault, title, fields, secret, verbose } = options;
123132

124-
const template = buildItemTemplate(title, vault, fields, secret);
125-
const json = JSON.stringify(template);
133+
const template = buildFieldsTemplate(fields, secret);
134+
const templatePath = writeTempTemplate(template);
126135

127136
try {
128-
const result = await execWithStdin("op", ["item", "create", "--format", "json"], { stdin: json, verbose });
137+
const result = await exec(
138+
"op",
139+
[
140+
"item",
141+
"create",
142+
"--category",
143+
"Secure Note",
144+
"--title",
145+
title,
146+
"--vault",
147+
vault,
148+
"--template",
149+
templatePath,
150+
"--format",
151+
"json",
152+
],
153+
{ verbose },
154+
);
129155

130156
if (result.exitCode !== 0) {
131157
throw new Error(result.stderr || "Failed to create item");
@@ -151,6 +177,8 @@ export async function createSecureNote(options: CreateItemOptions & VerboseOptio
151177
} catch (error) {
152178
const message = error instanceof Error ? error.message : String(error);
153179
throw errors.itemCreateFailed(message);
180+
} finally {
181+
cleanupTempFile(templatePath);
154182
}
155183
}
156184

@@ -162,14 +190,27 @@ export async function createSecureNote(options: CreateItemOptions & VerboseOptio
162190
export async function editSecureNote(options: EditItemOptions & VerboseOption): Promise<CreateItemResult> {
163191
const { vault, title, fields, secret, verbose, itemId } = options;
164192

165-
const template = buildItemTemplate(title, vault, fields, secret);
166-
const json = JSON.stringify(template);
193+
const template = buildFieldsTemplate(fields, secret);
194+
const templatePath = writeTempTemplate(template);
167195

168196
try {
169-
const result = await execWithStdin("op", ["item", "edit", itemId, "--format", "json"], {
170-
stdin: json,
171-
verbose,
172-
});
197+
const result = await exec(
198+
"op",
199+
[
200+
"item",
201+
"edit",
202+
itemId,
203+
"--title",
204+
title,
205+
"--vault",
206+
vault,
207+
"--template",
208+
templatePath,
209+
"--format",
210+
"json",
211+
],
212+
{ verbose },
213+
);
173214

174215
if (result.exitCode !== 0) {
175216
throw new Error(result.stderr || "Failed to edit item");
@@ -195,5 +236,7 @@ export async function editSecureNote(options: EditItemOptions & VerboseOption):
195236
} catch (error) {
196237
const message = error instanceof Error ? error.message : String(error);
197238
throw errors.itemEditFailed(message);
239+
} finally {
240+
cleanupTempFile(templatePath);
198241
}
199242
}

0 commit comments

Comments
 (0)