@simplysm/sd-cli 14.2.14 → 14.2.17
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/publish/deployment-phase.d.ts +1 -1
- package/dist/commands/publish/deployment-phase.d.ts.map +1 -1
- package/dist/commands/publish/deployment-phase.js +63 -51
- package/dist/commands/publish/deployment-phase.js.map +1 -1
- package/dist/commands/publish/npm-publisher.d.ts +13 -2
- package/dist/commands/publish/npm-publisher.d.ts.map +1 -1
- package/dist/commands/publish/npm-publisher.js +113 -11
- package/dist/commands/publish/npm-publisher.js.map +1 -1
- package/dist/commands/publish/publish-command.d.ts +2 -0
- package/dist/commands/publish/publish-command.d.ts.map +1 -1
- package/dist/commands/publish/publish-command.js +31 -3
- package/dist/commands/publish/publish-command.js.map +1 -1
- package/dist/sd-cli-entry.d.ts.map +1 -1
- package/dist/sd-cli-entry.js +5 -0
- package/dist/sd-cli-entry.js.map +1 -1
- package/package.json +4 -4
- package/src/commands/publish/deployment-phase.ts +89 -48
- package/src/commands/publish/npm-publisher.ts +131 -13
- package/src/commands/publish/publish-command.ts +38 -3
- package/src/sd-cli-entry.ts +5 -0
- package/tests/commands/deployment-phase.acc.spec.ts +148 -11
- package/tests/commands/npm-publisher.acc.spec.ts +360 -0
- package/tests/sd-cli-entry.spec.ts +20 -0
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import semver from "semver";
|
|
4
|
+
import { cpx, fsx } from "@simplysm/core-node";
|
|
5
|
+
|
|
6
|
+
const mocks = {
|
|
7
|
+
spawn: vi.spyOn(cpx, "spawn"),
|
|
8
|
+
mkdir: vi.spyOn(fsx, "mkdir"),
|
|
9
|
+
readdir: vi.spyOn(fsx, "readdir"),
|
|
10
|
+
rm: vi.spyOn(fsx, "rm"),
|
|
11
|
+
readJson: vi.spyOn(fsx, "readJson"),
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
import { publishNpm, validateOtp } from "../../src/commands/publish/npm-publisher";
|
|
15
|
+
|
|
16
|
+
const OTP = "123456";
|
|
17
|
+
const TARBALL = "simplysm-pkg-a-14.0.1.tgz";
|
|
18
|
+
|
|
19
|
+
function createMockLogger() {
|
|
20
|
+
return {
|
|
21
|
+
info: vi.fn(),
|
|
22
|
+
debug: vi.fn(),
|
|
23
|
+
error: vi.fn(),
|
|
24
|
+
warn: vi.fn(),
|
|
25
|
+
start: vi.fn(),
|
|
26
|
+
success: vi.fn(),
|
|
27
|
+
fail: vi.fn(),
|
|
28
|
+
} as unknown as ReturnType<typeof import("@simplysm/core-common").createLogger>;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function loggedMessages(logger: ReturnType<typeof createMockLogger>): string {
|
|
32
|
+
const calls = [
|
|
33
|
+
...(logger.debug as unknown as ReturnType<typeof vi.fn>).mock.calls,
|
|
34
|
+
...(logger.info as unknown as ReturnType<typeof vi.fn>).mock.calls,
|
|
35
|
+
];
|
|
36
|
+
return calls.map((c: unknown[]) => String(c[0])).join("\n");
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
interface SpawnCall {
|
|
40
|
+
cmd: string;
|
|
41
|
+
args: string[];
|
|
42
|
+
opts: { cwd?: string; stdio?: unknown };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* cpx.spawn 호출을 수집한다.
|
|
47
|
+
*
|
|
48
|
+
* `npm view <pkg> dist-tags.latest` 응답만 `registryLatest` 로 흉내내고, 나머지는 성공 처리한다.
|
|
49
|
+
* 반환 배열에는 publish 파이프라인 호출(pack, publish)만 담긴다.
|
|
50
|
+
*/
|
|
51
|
+
function captureSpawns(registryLatest?: string): SpawnCall[] {
|
|
52
|
+
const calls: SpawnCall[] = [];
|
|
53
|
+
mocks.spawn.mockImplementation(((
|
|
54
|
+
cmd: string,
|
|
55
|
+
args?: string[],
|
|
56
|
+
opts?: { cwd?: string; stdio?: unknown },
|
|
57
|
+
) => {
|
|
58
|
+
const argList = args ?? [];
|
|
59
|
+
if (cmd === "npm" && argList[0] === "view") {
|
|
60
|
+
if (registryLatest == null) throw new Error("E404 not found");
|
|
61
|
+
return { stdout: `${registryLatest}\n`, stderr: "", exitCode: 0 };
|
|
62
|
+
}
|
|
63
|
+
calls.push({ cmd, args: argList, opts: opts ?? {} });
|
|
64
|
+
return { stdout: "", stderr: "", exitCode: 0 };
|
|
65
|
+
}) as never);
|
|
66
|
+
return calls;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
describe("validateOtp", () => {
|
|
70
|
+
it("accepts 6 digits and trims", () => {
|
|
71
|
+
expect(validateOtp(" 012345 ")).toBe("012345");
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("rejects anything else", () => {
|
|
75
|
+
for (const bad of ["12345", "1234567", "abcdef", "123456; rm -rf /", ""]) {
|
|
76
|
+
expect(() => validateOtp(bad)).toThrow("OTP 는 6자리 숫자여야 합니다.");
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
describe("publishNpm", () => {
|
|
82
|
+
beforeEach(() => {
|
|
83
|
+
vi.clearAllMocks();
|
|
84
|
+
mocks.mkdir.mockResolvedValue(undefined);
|
|
85
|
+
mocks.rm.mockResolvedValue(undefined);
|
|
86
|
+
mocks.readdir.mockResolvedValue([TARBALL]);
|
|
87
|
+
mocks.readJson.mockResolvedValue({ name: "@simplysm/pkg-a" });
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it("packs with pnpm and publishes the tarball with npm", async () => {
|
|
91
|
+
const calls = captureSpawns();
|
|
92
|
+
|
|
93
|
+
const logger = createMockLogger();
|
|
94
|
+
await publishNpm("/tmp/pkg-a", "pkg-a", "14.0.1", logger, false, undefined);
|
|
95
|
+
|
|
96
|
+
expect(calls).toHaveLength(2);
|
|
97
|
+
// workspace: 치환과 publishConfig 머지는 pnpm pack 이 해준다
|
|
98
|
+
expect(calls[0].cmd).toBe("pnpm");
|
|
99
|
+
expect(calls[0].args[0]).toBe("pack");
|
|
100
|
+
// 2FA 인증은 npm 이 처리하므로 tarball 을 npm 으로 올린다
|
|
101
|
+
expect(calls[1].cmd).toBe("npm");
|
|
102
|
+
expect(calls[1].args[0]).toBe("publish");
|
|
103
|
+
expect(calls[1].args.join(" ")).toContain(TARBALL);
|
|
104
|
+
expect(calls[1].args[calls[1].args.indexOf("--access") + 1]).toBe("public");
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("looks up the registry latest by the npm package name, not the directory name", async () => {
|
|
108
|
+
const viewed: string[] = [];
|
|
109
|
+
mocks.spawn.mockImplementation(((cmd: string, args?: string[]) => {
|
|
110
|
+
const argList = args ?? [];
|
|
111
|
+
if (cmd === "npm" && argList[0] === "view") {
|
|
112
|
+
viewed.push(argList[1]);
|
|
113
|
+
return { stdout: "14.2.14\n", stderr: "", exitCode: 0 };
|
|
114
|
+
}
|
|
115
|
+
return { stdout: "", stderr: "", exitCode: 0 };
|
|
116
|
+
}) as never);
|
|
117
|
+
mocks.readJson.mockResolvedValue({ name: "@simplysm/core-common" });
|
|
118
|
+
|
|
119
|
+
const logger = createMockLogger();
|
|
120
|
+
await publishNpm("/tmp/core-common", "core-common", "14.2.15", logger, false, undefined);
|
|
121
|
+
|
|
122
|
+
expect(viewed).toEqual(['"@simplysm/core-common"']);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it("hands the terminal over to npm so it can run its own auth flow", async () => {
|
|
126
|
+
const calls = captureSpawns();
|
|
127
|
+
|
|
128
|
+
const logger = createMockLogger();
|
|
129
|
+
await publishNpm("/tmp/pkg-a", "pkg-a", "14.0.1", logger, false, undefined);
|
|
130
|
+
|
|
131
|
+
// pack 은 로그를 캡처하고, publish 는 TTY 를 그대로 물려준다
|
|
132
|
+
expect(calls[0].opts.stdio).toBeUndefined();
|
|
133
|
+
expect(calls[1].opts.stdio).toBe("inherit");
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it("cleans up the temporary tarball directory when pack fails", async () => {
|
|
137
|
+
mocks.spawn.mockImplementation(() => {
|
|
138
|
+
throw new Error("pack failed");
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
const logger = createMockLogger();
|
|
142
|
+
await expect(
|
|
143
|
+
publishNpm("/tmp/pkg-a", "pkg-a", "14.0.1", logger, false, undefined),
|
|
144
|
+
).rejects.toThrow("pack failed");
|
|
145
|
+
|
|
146
|
+
expect(mocks.rm).toHaveBeenCalledTimes(1);
|
|
147
|
+
expect(String(mocks.rm.mock.calls[0][0])).toContain("sd-cli-pack-pkg-a");
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it("cleans up the temporary tarball directory when publish fails", async () => {
|
|
151
|
+
mocks.spawn.mockImplementation(((cmd: string, args?: string[]) => {
|
|
152
|
+
if (cmd === "npm" && args?.[0] === "publish") throw new Error("publish failed");
|
|
153
|
+
return { stdout: "", stderr: "", exitCode: 0 };
|
|
154
|
+
}) as never);
|
|
155
|
+
|
|
156
|
+
const logger = createMockLogger();
|
|
157
|
+
await expect(
|
|
158
|
+
publishNpm("/tmp/pkg-a", "pkg-a", "14.0.1", logger, false, undefined),
|
|
159
|
+
).rejects.toThrow("publish failed");
|
|
160
|
+
|
|
161
|
+
expect(mocks.rm).toHaveBeenCalledTimes(1);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it("points to the npm output because stdio was handed over", async () => {
|
|
165
|
+
mocks.spawn.mockImplementation(((cmd: string, args?: string[]) => {
|
|
166
|
+
if (cmd === "npm" && args?.[0] === "publish") throw new Error("Command failed (exit 1)");
|
|
167
|
+
return { stdout: "", stderr: "", exitCode: 0 };
|
|
168
|
+
}) as never);
|
|
169
|
+
|
|
170
|
+
const logger = createMockLogger();
|
|
171
|
+
await expect(
|
|
172
|
+
publishNpm("/tmp/pkg-a", "pkg-a", "14.0.1", logger, false, undefined),
|
|
173
|
+
).rejects.toThrow("위 npm 출력을 확인하세요");
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it("does not let a cleanup failure hide the publish failure", async () => {
|
|
177
|
+
mocks.spawn.mockImplementation(((cmd: string, args?: string[]) => {
|
|
178
|
+
if (cmd === "npm" && args?.[0] === "publish") throw new Error("publish failed");
|
|
179
|
+
return { stdout: "", stderr: "", exitCode: 0 };
|
|
180
|
+
}) as never);
|
|
181
|
+
mocks.rm.mockRejectedValue(new Error("EBUSY"));
|
|
182
|
+
|
|
183
|
+
const logger = createMockLogger();
|
|
184
|
+
await expect(
|
|
185
|
+
publishNpm("/tmp/pkg-a", "pkg-a", "14.0.1", logger, false, undefined),
|
|
186
|
+
).rejects.toThrow("publish failed");
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it("fails when pack produced no tarball", async () => {
|
|
190
|
+
captureSpawns();
|
|
191
|
+
mocks.readdir.mockResolvedValue([]);
|
|
192
|
+
|
|
193
|
+
const logger = createMockLogger();
|
|
194
|
+
await expect(
|
|
195
|
+
publishNpm("/tmp/pkg-a", "pkg-a", "14.0.1", logger, false, undefined),
|
|
196
|
+
).rejects.toThrow("tarball 을 찾을 수 없습니다");
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it("adds a prerelease tag from the version", async () => {
|
|
200
|
+
const calls = captureSpawns();
|
|
201
|
+
|
|
202
|
+
const logger = createMockLogger();
|
|
203
|
+
await publishNpm("/tmp/pkg-a", "pkg-a", "14.0.1-beta.3", logger, false, undefined);
|
|
204
|
+
|
|
205
|
+
const args = calls[1].args;
|
|
206
|
+
expect(args[args.indexOf("--tag") + 1]).toBe("beta");
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it("never lets a prerelease become latest, even with a numeric identifier", async () => {
|
|
210
|
+
const calls = captureSpawns("14.0.0");
|
|
211
|
+
|
|
212
|
+
const logger = createMockLogger();
|
|
213
|
+
// semver.inc(v, "prerelease") 는 `14.3.0-1` 같은 숫자 식별자를 만든다
|
|
214
|
+
await publishNpm("/tmp/pkg-a", "pkg-a", "14.3.0-1", logger, false, undefined);
|
|
215
|
+
|
|
216
|
+
const args = calls[1].args;
|
|
217
|
+
const tag = args[args.indexOf("--tag") + 1];
|
|
218
|
+
expect(args).toContain("--tag");
|
|
219
|
+
expect(semver.validRange(tag)).toBeNull();
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
it("adds a major tag when the registry latest is higher", async () => {
|
|
223
|
+
const calls = captureSpawns("15.1.15");
|
|
224
|
+
|
|
225
|
+
const logger = createMockLogger();
|
|
226
|
+
await publishNpm("/tmp/pkg-a", "pkg-a", "14.2.15", logger, false, undefined);
|
|
227
|
+
|
|
228
|
+
// latest 를 15.1.15 에서 끌어내리지 않으려면 별도 태그가 필요하다
|
|
229
|
+
const args = calls[1].args;
|
|
230
|
+
expect(args[args.indexOf("--tag") + 1]).toBe("latest-14");
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it("never uses a tag name npm would read as a semver range", async () => {
|
|
234
|
+
const calls = captureSpawns("15.1.15");
|
|
235
|
+
|
|
236
|
+
const logger = createMockLogger();
|
|
237
|
+
await publishNpm("/tmp/pkg-a", "pkg-a", "14.2.15", logger, false, undefined);
|
|
238
|
+
|
|
239
|
+
// npm 은 `v14`, `14.x` 처럼 범위로 해석되는 이름을 dist-tag 로 거부한다
|
|
240
|
+
const args = calls[1].args;
|
|
241
|
+
expect(semver.validRange(args[args.indexOf("--tag") + 1])).toBeNull();
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
it("does not tag when the version becomes the new latest", async () => {
|
|
245
|
+
const calls = captureSpawns("14.2.14");
|
|
246
|
+
|
|
247
|
+
const logger = createMockLogger();
|
|
248
|
+
await publishNpm("/tmp/pkg-a", "pkg-a", "14.2.15", logger, false, undefined);
|
|
249
|
+
|
|
250
|
+
expect(calls[1].args).not.toContain("--tag");
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
it("does not tag a package that was never published", async () => {
|
|
254
|
+
const calls = captureSpawns();
|
|
255
|
+
|
|
256
|
+
const logger = createMockLogger();
|
|
257
|
+
await publishNpm("/tmp/pkg-a", "pkg-a", "14.2.15", logger, false, undefined);
|
|
258
|
+
|
|
259
|
+
expect(calls[1].args).not.toContain("--tag");
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
it("passes the OTP to npm but masks it in logs", async () => {
|
|
263
|
+
const calls = captureSpawns();
|
|
264
|
+
|
|
265
|
+
const logger = createMockLogger();
|
|
266
|
+
await publishNpm("/tmp/pkg-a", "pkg-a", "14.0.1", logger, false, OTP);
|
|
267
|
+
|
|
268
|
+
const args = calls[1].args;
|
|
269
|
+
expect(args[args.indexOf("--otp") + 1]).toBe(OTP);
|
|
270
|
+
|
|
271
|
+
const logged = loggedMessages(logger);
|
|
272
|
+
expect(logged).not.toContain(OTP);
|
|
273
|
+
expect(logged).toContain("******");
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
it("does not add --otp when no code is given", async () => {
|
|
277
|
+
const calls = captureSpawns();
|
|
278
|
+
|
|
279
|
+
const logger = createMockLogger();
|
|
280
|
+
await publishNpm("/tmp/pkg-a", "pkg-a", "14.0.1", logger, false, undefined);
|
|
281
|
+
|
|
282
|
+
expect(calls[1].args).not.toContain("--otp");
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
it("masks the OTP in the failure message and keeps the original stack", async () => {
|
|
286
|
+
mocks.spawn.mockImplementation(((cmd: string) => {
|
|
287
|
+
if (cmd === "pnpm") return { stdout: "", stderr: "", exitCode: 0 };
|
|
288
|
+
// 실제 실패 메시지에는 실행된 명령줄이 그대로 담긴다
|
|
289
|
+
const err = new Error(`Command failed (exit 1): npm publish --otp ${OTP}\nEOTP required`);
|
|
290
|
+
err.stack = `Error: publish failed with --otp ${OTP}\n at spawnedByNpmPublish (cp.ts:1:1)`;
|
|
291
|
+
throw err;
|
|
292
|
+
}) as never);
|
|
293
|
+
|
|
294
|
+
const logger = createMockLogger();
|
|
295
|
+
const err = await publishNpm("/tmp/pkg-a", "pkg-a", "14.0.1", logger, false, OTP).then(
|
|
296
|
+
() => null,
|
|
297
|
+
(e: unknown) => e,
|
|
298
|
+
);
|
|
299
|
+
|
|
300
|
+
const asError = err as Error;
|
|
301
|
+
expect(asError.message).toContain("EOTP");
|
|
302
|
+
expect(asError.message).not.toContain(OTP);
|
|
303
|
+
expect(asError.message).toContain("******");
|
|
304
|
+
expect(asError.stack ?? "").toContain("spawnedByNpmPublish");
|
|
305
|
+
expect(asError.stack ?? "").not.toContain(OTP);
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
it("masks a padded OTP consistently", async () => {
|
|
309
|
+
let publishArgs: string[] = [];
|
|
310
|
+
mocks.spawn.mockImplementation(((cmd: string, args?: string[]) => {
|
|
311
|
+
const argList = args ?? [];
|
|
312
|
+
if (!(cmd === "npm" && argList[0] === "publish")) {
|
|
313
|
+
return { stdout: "", stderr: "", exitCode: 0 };
|
|
314
|
+
}
|
|
315
|
+
publishArgs = argList;
|
|
316
|
+
throw new Error(`Command failed: npm ${argList.join(" ")}`);
|
|
317
|
+
}) as never);
|
|
318
|
+
|
|
319
|
+
const logger = createMockLogger();
|
|
320
|
+
const err = await publishNpm("/tmp/pkg-a", "pkg-a", "14.0.1", logger, false, ` ${OTP} `).then(
|
|
321
|
+
() => null,
|
|
322
|
+
(e: unknown) => e,
|
|
323
|
+
);
|
|
324
|
+
|
|
325
|
+
// 명령줄에 trim 된 값이 들어가고, 마스킹 대상도 같은 값이어야 한다
|
|
326
|
+
expect(publishArgs[publishArgs.indexOf("--otp") + 1]).toBe(OTP);
|
|
327
|
+
expect((err as Error).message).not.toContain(OTP);
|
|
328
|
+
expect((err as Error).message).toContain("******");
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
it("rejects a malformed OTP", async () => {
|
|
332
|
+
captureSpawns();
|
|
333
|
+
|
|
334
|
+
const logger = createMockLogger();
|
|
335
|
+
await expect(
|
|
336
|
+
publishNpm("/tmp/pkg-a", "pkg-a", "14.0.1", logger, false, "12345; rm -rf /"),
|
|
337
|
+
).rejects.toThrow("OTP 는 6자리 숫자여야 합니다.");
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
it("adds --dry-run instead of publishing for real", async () => {
|
|
341
|
+
const calls = captureSpawns();
|
|
342
|
+
|
|
343
|
+
const logger = createMockLogger();
|
|
344
|
+
await publishNpm("/tmp/pkg-a", "pkg-a", "14.0.1", logger, true, undefined);
|
|
345
|
+
|
|
346
|
+
expect(calls[1].args).toContain("--dry-run");
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
it("quotes paths so spaces cannot split the shell command", async () => {
|
|
350
|
+
const calls = captureSpawns();
|
|
351
|
+
|
|
352
|
+
const logger = createMockLogger();
|
|
353
|
+
await publishNpm("/tmp/pkg-a", "pkg-a", "14.0.1", logger, false, undefined);
|
|
354
|
+
|
|
355
|
+
const packDest = calls[0].args[calls[0].args.indexOf("--pack-destination") + 1];
|
|
356
|
+
expect(packDest.startsWith('"')).toBe(true);
|
|
357
|
+
expect(packDest.endsWith('"')).toBe(true);
|
|
358
|
+
expect(calls[1].args[1]).toBe(`"${path.join(packDest.slice(1, -1), TARBALL)}"`);
|
|
359
|
+
});
|
|
360
|
+
});
|
|
@@ -61,6 +61,26 @@ describe("sd-cli-entry createCliParser", () => {
|
|
|
61
61
|
).resolves.toBeDefined();
|
|
62
62
|
});
|
|
63
63
|
|
|
64
|
+
it("passes publish --otp through to runPublish", async () => {
|
|
65
|
+
const { createCliParser } = await import("../src/sd-cli-entry");
|
|
66
|
+
|
|
67
|
+
await createCliParser(["publish", "--otp", "012345"]).exitProcess(false).parse();
|
|
68
|
+
|
|
69
|
+
expect(publishCmd.runPublish).toHaveBeenCalledWith(
|
|
70
|
+
expect.objectContaining({ otp: "012345" }),
|
|
71
|
+
);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("leaves publish otp undefined when not given", async () => {
|
|
75
|
+
const { createCliParser } = await import("../src/sd-cli-entry");
|
|
76
|
+
|
|
77
|
+
await createCliParser(["publish"]).exitProcess(false).parse();
|
|
78
|
+
|
|
79
|
+
expect(publishCmd.runPublish).toHaveBeenCalledWith(
|
|
80
|
+
expect.objectContaining({ otp: undefined }),
|
|
81
|
+
);
|
|
82
|
+
});
|
|
83
|
+
|
|
64
84
|
it("throws on --type test (removed type)", async () => {
|
|
65
85
|
const { createCliParser } = await import("../src/sd-cli-entry");
|
|
66
86
|
|