zinkee 0.1.44 → 0.1.46
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/README.md +4 -0
- package/dist/chunk-MHEVRMDX.js +1230 -0
- package/dist/chunk-MHEVRMDX.js.map +1 -0
- package/{src/utils/examples.ts → dist/examples-67U5IRBS.js} +866 -1141
- package/dist/examples-67U5IRBS.js.map +1 -0
- package/dist/index.js +1145 -8456
- package/dist/index.js.map +1 -1
- package/package.json +6 -3
- package/.github/workflows/npm-publish.yml +0 -77
- package/.github/workflows/pr-checks.yml +0 -36
- package/AGENTS.md +0 -110
- package/docs/cli-contract.md +0 -41
- package/docs/npm-release.md +0 -64
- package/docs/superpowers/plans/2026-03-24-zinkee-cli-implementation.md +0 -837
- package/docs/superpowers/plans/2026-03-25-cli-backend-error-contract.md +0 -503
- package/docs/superpowers/plans/2026-03-26-display-freeform-create.md +0 -389
- package/docs/superpowers/specs/2026-03-24-zinkee-cli-backend-blockers.md +0 -172
- package/docs/superpowers/specs/2026-03-24-zinkee-cli-design.md +0 -1576
- package/docs/superpowers/specs/2026-03-24-zinkee-cli-e2e-checklist.md +0 -215
- package/docs/superpowers/specs/2026-03-24-zinkee-cli-e2e-design.md +0 -492
- package/docs/superpowers/specs/2026-03-24-zinkee-cli-e2e-status.md +0 -307
- package/docs/superpowers/specs/2026-07-30-cli-pr-checks-design.md +0 -37
- package/src/api/automations.ts +0 -404
- package/src/api/comments.ts +0 -51
- package/src/api/displays.ts +0 -337
- package/src/api/document-templates.ts +0 -110
- package/src/api/files.ts +0 -50
- package/src/api/formulas.test.ts +0 -65
- package/src/api/formulas.ts +0 -67
- package/src/api/logs.ts +0 -34
- package/src/api/navigation.ts +0 -70
- package/src/api/records.ts +0 -184
- package/src/api/schemas.ts +0 -148
- package/src/api/teamspace.ts +0 -110
- package/src/cli-examples.ts +0 -130
- package/src/cli-runner.ts +0 -95
- package/src/client.test.ts +0 -189
- package/src/client.ts +0 -269
- package/src/command-registry.ts +0 -882
- package/src/commands/automations.test.ts +0 -1030
- package/src/commands/automations.ts +0 -2102
- package/src/commands/comments.test.ts +0 -214
- package/src/commands/comments.ts +0 -303
- package/src/commands/config.test.ts +0 -81
- package/src/commands/config.ts +0 -150
- package/src/commands/displays.test.ts +0 -1105
- package/src/commands/displays.ts +0 -1442
- package/src/commands/document-templates.test.ts +0 -569
- package/src/commands/document-templates.ts +0 -563
- package/src/commands/files.test.ts +0 -284
- package/src/commands/files.ts +0 -280
- package/src/commands/formulas.test.ts +0 -194
- package/src/commands/formulas.ts +0 -243
- package/src/commands/logs.test.ts +0 -123
- package/src/commands/logs.ts +0 -159
- package/src/commands/navigation.test.ts +0 -211
- package/src/commands/navigation.ts +0 -348
- package/src/commands/profiles.test.ts +0 -191
- package/src/commands/profiles.ts +0 -303
- package/src/commands/records.test.ts +0 -860
- package/src/commands/records.ts +0 -883
- package/src/commands/schemas.test.ts +0 -1252
- package/src/commands/schemas.ts +0 -890
- package/src/commands/teamspace.test.ts +0 -229
- package/src/commands/teamspace.ts +0 -546
- package/src/completion/engine.test.ts +0 -138
- package/src/completion/engine.ts +0 -168
- package/src/completion/install.test.ts +0 -179
- package/src/completion/install.ts +0 -260
- package/src/completion/runtime.ts +0 -150
- package/src/completion/scripts.ts +0 -91
- package/src/config.test.ts +0 -362
- package/src/config.ts +0 -294
- package/src/index.test.ts +0 -217
- package/src/index.ts +0 -8
- package/src/parsers/expressions.test.ts +0 -95
- package/src/parsers/expressions.ts +0 -128
- package/src/parsers/kv.test.ts +0 -35
- package/src/parsers/kv.ts +0 -49
- package/src/parsers/selectors.test.ts +0 -23
- package/src/parsers/selectors.ts +0 -29
- package/src/program.ts +0 -64
- package/src/runtime-context.ts +0 -103
- package/src/types.ts +0 -76
- package/src/utils/argv-rewrite.test.ts +0 -149
- package/src/utils/argv-rewrite.ts +0 -269
- package/src/utils/errors.test.ts +0 -85
- package/src/utils/errors.ts +0 -191
- package/src/utils/examples.test.ts +0 -374
- package/src/utils/output.test.ts +0 -65
- package/src/utils/output.ts +0 -154
- package/src/utils/schema-fields.ts +0 -1016
- package/tsconfig.json +0 -20
- package/tsup.config.ts +0 -13
|
@@ -1,546 +0,0 @@
|
|
|
1
|
-
import { readFileSync } from "node:fs";
|
|
2
|
-
import type { Writable } from "node:stream";
|
|
3
|
-
|
|
4
|
-
import { Command, InvalidArgumentError } from "commander";
|
|
5
|
-
|
|
6
|
-
import {
|
|
7
|
-
TeamspaceApi,
|
|
8
|
-
type ApiTeamspaceFolder,
|
|
9
|
-
type CreateTeamspaceFolderInput,
|
|
10
|
-
type UpdateTeamspaceFolderInput,
|
|
11
|
-
} from "../api/teamspace.js";
|
|
12
|
-
import { ApiClient } from "../client.js";
|
|
13
|
-
import { defaultConfigPath, readRuntimeConfig } from "../config.js";
|
|
14
|
-
import {
|
|
15
|
-
normalizeUuidOrSlug,
|
|
16
|
-
type NormalizedUuidOrSlug,
|
|
17
|
-
} from "../parsers/selectors.js";
|
|
18
|
-
import { resolveRuntimeContext } from "../runtime-context.js";
|
|
19
|
-
import type { JsonObject, RuntimeOverrides } from "../types.js";
|
|
20
|
-
import { CliError } from "../utils/errors.js";
|
|
21
|
-
import { buildJsonSuccess, printTable } from "../utils/output.js";
|
|
22
|
-
|
|
23
|
-
interface CommandIo {
|
|
24
|
-
write(chunk: string): unknown;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export interface RegisterTeamspaceCommandsOptions {
|
|
28
|
-
configPath?: string;
|
|
29
|
-
stdout?: CommandIo;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
interface GlobalOptions extends RuntimeOverrides {}
|
|
33
|
-
|
|
34
|
-
interface FolderListCommandOptions {
|
|
35
|
-
includeResources?: boolean;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
interface FolderCreateCommandOptions {
|
|
39
|
-
name?: string;
|
|
40
|
-
order?: number;
|
|
41
|
-
props?: string;
|
|
42
|
-
propsFile?: string;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
interface FolderUpdateCommandOptions extends FolderCreateCommandOptions {}
|
|
46
|
-
|
|
47
|
-
interface ResourcePublishCommandOptions {
|
|
48
|
-
to: NormalizedUuidOrSlug;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function getCommandIo(stdout: CommandIo | undefined): CommandIo {
|
|
52
|
-
return stdout ?? (process.stdout as Writable);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
function writeLine(stdout: CommandIo, value: string): void {
|
|
56
|
-
stdout.write(`${value}\n`);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function getGlobalOptions(command: Command): GlobalOptions {
|
|
60
|
-
return command.optsWithGlobals<GlobalOptions>();
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function emitJson(
|
|
64
|
-
stdout: CommandIo,
|
|
65
|
-
commandName: string,
|
|
66
|
-
data: unknown,
|
|
67
|
-
configPath: string,
|
|
68
|
-
): void {
|
|
69
|
-
writeLine(
|
|
70
|
-
stdout,
|
|
71
|
-
JSON.stringify(buildJsonSuccess(data, { command: commandName, configPath })),
|
|
72
|
-
);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function toConfigurationError(filePath: string, error: unknown): never {
|
|
76
|
-
if (error instanceof CliError) {
|
|
77
|
-
throw error;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
if (
|
|
81
|
-
typeof error === "object" &&
|
|
82
|
-
error !== null &&
|
|
83
|
-
"code" in error &&
|
|
84
|
-
error.code === "ENOENT"
|
|
85
|
-
) {
|
|
86
|
-
throw new CliError("configuration_error", `Config file not found at ${filePath}.`);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
if (error instanceof Error) {
|
|
90
|
-
throw new CliError("configuration_error", error.message);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
throw error;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
function createTeamspaceApi(
|
|
97
|
-
command: Command,
|
|
98
|
-
filePath: string,
|
|
99
|
-
): { api: TeamspaceApi; json: boolean; readOnly: boolean } {
|
|
100
|
-
try {
|
|
101
|
-
const config = readRuntimeConfig(filePath);
|
|
102
|
-
const runtime = resolveRuntimeContext(config, getGlobalOptions(command));
|
|
103
|
-
|
|
104
|
-
if (!runtime.baseUrl) {
|
|
105
|
-
throw new CliError(
|
|
106
|
-
"configuration_error",
|
|
107
|
-
`Resolved profile "${runtime.profile}" is missing a base URL.`,
|
|
108
|
-
);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
return {
|
|
112
|
-
api: new TeamspaceApi(
|
|
113
|
-
new ApiClient({
|
|
114
|
-
baseUrl: runtime.baseUrl,
|
|
115
|
-
apiKey: runtime.apiKey,
|
|
116
|
-
}),
|
|
117
|
-
),
|
|
118
|
-
json: runtime.json,
|
|
119
|
-
readOnly: runtime.readOnly,
|
|
120
|
-
};
|
|
121
|
-
} catch (error) {
|
|
122
|
-
toConfigurationError(filePath, error);
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
function ensureWritable(
|
|
127
|
-
commandName: string,
|
|
128
|
-
readOnly: boolean,
|
|
129
|
-
commandMode: "write" | "destructive",
|
|
130
|
-
): void {
|
|
131
|
-
if (readOnly) {
|
|
132
|
-
throw new CliError(
|
|
133
|
-
"command_not_allowed_in_read_only_mode",
|
|
134
|
-
`Command "${commandName}" is not allowed in read-only mode.`,
|
|
135
|
-
{
|
|
136
|
-
commandMode,
|
|
137
|
-
executionMode: "read-only",
|
|
138
|
-
},
|
|
139
|
-
);
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
function parseUuidOrSlugSelector(input: string): NormalizedUuidOrSlug {
|
|
144
|
-
try {
|
|
145
|
-
return normalizeUuidOrSlug(input);
|
|
146
|
-
} catch (error) {
|
|
147
|
-
const message = error instanceof Error ? error.message : "Invalid selector.";
|
|
148
|
-
throw new InvalidArgumentError(message);
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
function parseNonNegativeInteger(input: string): number {
|
|
153
|
-
if (!/^\d+$/.test(input.trim())) {
|
|
154
|
-
throw new InvalidArgumentError(`Expected a non-negative integer, received "${input}".`);
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
return Number(input);
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
function isJsonObject(value: unknown): value is JsonObject {
|
|
161
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
function parseRawJsonObject(input: string, sourceLabel: string): JsonObject {
|
|
165
|
-
let parsed: unknown;
|
|
166
|
-
|
|
167
|
-
try {
|
|
168
|
-
parsed = JSON.parse(input);
|
|
169
|
-
} catch (error) {
|
|
170
|
-
const message = error instanceof Error ? error.message : "Invalid JSON.";
|
|
171
|
-
throw new CliError(
|
|
172
|
-
"invalid_cli_usage",
|
|
173
|
-
`Failed to parse JSON from ${sourceLabel}: ${message}`,
|
|
174
|
-
);
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
if (!isJsonObject(parsed)) {
|
|
178
|
-
throw new CliError(
|
|
179
|
-
"invalid_cli_usage",
|
|
180
|
-
`Expected ${sourceLabel} to contain a JSON object.`,
|
|
181
|
-
);
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
return parsed;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
function readRawObject(
|
|
188
|
-
raw: string | undefined,
|
|
189
|
-
rawFile: string | undefined,
|
|
190
|
-
): JsonObject | undefined {
|
|
191
|
-
if (raw && rawFile) {
|
|
192
|
-
throw new CliError(
|
|
193
|
-
"invalid_cli_usage",
|
|
194
|
-
'Provide either "--props" or "--props-file", not both.',
|
|
195
|
-
);
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
if (raw) {
|
|
199
|
-
return parseRawJsonObject(raw, "--props");
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
if (rawFile) {
|
|
203
|
-
let contents: string;
|
|
204
|
-
|
|
205
|
-
try {
|
|
206
|
-
contents = readFileSync(rawFile, "utf8");
|
|
207
|
-
} catch (error) {
|
|
208
|
-
const message = error instanceof Error ? error.message : "Unable to read file.";
|
|
209
|
-
throw new CliError(
|
|
210
|
-
"invalid_cli_usage",
|
|
211
|
-
`Failed to read props payload from ${rawFile}: ${message}`,
|
|
212
|
-
);
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
return parseRawJsonObject(contents, `--props-file ${rawFile}`);
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
return undefined;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
function isEmptyObject(value: object): boolean {
|
|
222
|
-
return Object.keys(value).length === 0;
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
function buildFolderCreateInput(
|
|
226
|
-
options: FolderCreateCommandOptions,
|
|
227
|
-
): CreateTeamspaceFolderInput {
|
|
228
|
-
if (options.name === undefined) {
|
|
229
|
-
throw new CliError("invalid_cli_usage", 'Provide folder name via "--name".');
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
const input: CreateTeamspaceFolderInput = {
|
|
233
|
-
name: options.name,
|
|
234
|
-
};
|
|
235
|
-
|
|
236
|
-
if (options.order !== undefined) {
|
|
237
|
-
input.order = options.order;
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
const props = readRawObject(options.props, options.propsFile);
|
|
241
|
-
|
|
242
|
-
if (props !== undefined) {
|
|
243
|
-
input.additionalProperties = props;
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
return input;
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
function buildFolderUpdateInput(
|
|
250
|
-
options: FolderUpdateCommandOptions,
|
|
251
|
-
): UpdateTeamspaceFolderInput {
|
|
252
|
-
const input: UpdateTeamspaceFolderInput = {};
|
|
253
|
-
|
|
254
|
-
if (options.name !== undefined) {
|
|
255
|
-
input.name = options.name;
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
if (options.order !== undefined) {
|
|
259
|
-
input.order = options.order;
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
const props = readRawObject(options.props, options.propsFile);
|
|
263
|
-
|
|
264
|
-
if (props !== undefined) {
|
|
265
|
-
input.additionalProperties = props;
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
if (isEmptyObject(input)) {
|
|
269
|
-
throw new CliError(
|
|
270
|
-
"invalid_cli_usage",
|
|
271
|
-
'Provide folder updates via "--name", "--order", "--props", or "--props-file".',
|
|
272
|
-
);
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
return input;
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
function toTableRows(data: unknown): Record<string, unknown>[] {
|
|
279
|
-
if (Array.isArray(data)) {
|
|
280
|
-
return data.map((entry) => (isJsonObject(entry) ? entry : { value: entry }));
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
if (isJsonObject(data)) {
|
|
284
|
-
return [data];
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
return [{ value: data }];
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
function emitTable(stdout: CommandIo, data: unknown): void {
|
|
291
|
-
printTable(toTableRows(data), undefined, stdout);
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
function getFolderIdentifier(folder: ApiTeamspaceFolder): string {
|
|
295
|
-
return typeof folder.id === "string"
|
|
296
|
-
? folder.id
|
|
297
|
-
: typeof folder.name === "string"
|
|
298
|
-
? folder.name
|
|
299
|
-
: "unknown";
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
function buildFolderLookup(
|
|
303
|
-
folders: readonly ApiTeamspaceFolder[],
|
|
304
|
-
selector: NormalizedUuidOrSlug,
|
|
305
|
-
): NormalizedUuidOrSlug {
|
|
306
|
-
if (selector.kind === "uuid") {
|
|
307
|
-
return selector;
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
const matches = folders.filter((folder) => folder.name === selector.value);
|
|
311
|
-
|
|
312
|
-
if (matches.length === 1) {
|
|
313
|
-
const folderId = matches[0]?.id;
|
|
314
|
-
|
|
315
|
-
if (typeof folderId === "string" && folderId.length > 0) {
|
|
316
|
-
return {
|
|
317
|
-
kind: "uuid",
|
|
318
|
-
value: folderId,
|
|
319
|
-
};
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
if (matches.length > 1) {
|
|
324
|
-
throw new CliError(
|
|
325
|
-
"invalid_cli_usage",
|
|
326
|
-
`Folder "${selector.value}" is ambiguous. Found ${matches.length} matches.`,
|
|
327
|
-
);
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
throw new CliError("invalid_cli_usage", `Folder "${selector.value}" was not found.`);
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
function findFolderEntity(
|
|
334
|
-
folders: readonly ApiTeamspaceFolder[],
|
|
335
|
-
selector: NormalizedUuidOrSlug,
|
|
336
|
-
): ApiTeamspaceFolder {
|
|
337
|
-
if (selector.kind === "uuid") {
|
|
338
|
-
const match = folders.find((folder) => folder.id === selector.value);
|
|
339
|
-
|
|
340
|
-
if (match) {
|
|
341
|
-
return match;
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
throw new CliError("invalid_cli_usage", `Folder "${selector.value}" was not found.`);
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
const matches = folders.filter((folder) => folder.name === selector.value);
|
|
348
|
-
|
|
349
|
-
if (matches.length === 1) {
|
|
350
|
-
return matches[0] as ApiTeamspaceFolder;
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
if (matches.length > 1) {
|
|
354
|
-
throw new CliError(
|
|
355
|
-
"invalid_cli_usage",
|
|
356
|
-
`Folder "${selector.value}" is ambiguous. Found ${matches.length} matches.`,
|
|
357
|
-
);
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
throw new CliError("invalid_cli_usage", `Folder "${selector.value}" was not found.`);
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
async function resolveFolderSelector(
|
|
364
|
-
api: TeamspaceApi,
|
|
365
|
-
selector: NormalizedUuidOrSlug,
|
|
366
|
-
): Promise<NormalizedUuidOrSlug> {
|
|
367
|
-
if (selector.kind === "uuid") {
|
|
368
|
-
return selector;
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
const folders = await api.listFolders(false);
|
|
372
|
-
return buildFolderLookup(folders, selector);
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
export function registerTeamspaceCommands(
|
|
376
|
-
program: Command,
|
|
377
|
-
options: RegisterTeamspaceCommandsOptions = {},
|
|
378
|
-
): void {
|
|
379
|
-
const filePath = options.configPath ?? defaultConfigPath;
|
|
380
|
-
const stdout = getCommandIo(options.stdout);
|
|
381
|
-
const teamspace = program.command("teamspace").description("Manage teamspace folders and resources");
|
|
382
|
-
|
|
383
|
-
const folders = teamspace.command("folders").description("Manage teamspace folders");
|
|
384
|
-
|
|
385
|
-
folders
|
|
386
|
-
.command("list")
|
|
387
|
-
.description("List teamspace folders")
|
|
388
|
-
.option("--include-resources", "include the published resources inside each folder")
|
|
389
|
-
.action(async function action(this: Command, commandOptions: FolderListCommandOptions) {
|
|
390
|
-
const { api, json } = createTeamspaceApi(this, filePath);
|
|
391
|
-
const data = await api.listFolders(Boolean(commandOptions.includeResources));
|
|
392
|
-
|
|
393
|
-
if (json) {
|
|
394
|
-
emitJson(stdout, "teamspace folders list", data, filePath);
|
|
395
|
-
return;
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
emitTable(stdout, data);
|
|
399
|
-
});
|
|
400
|
-
|
|
401
|
-
folders
|
|
402
|
-
.command("get")
|
|
403
|
-
.description("Get a teamspace folder by exact name or UUID")
|
|
404
|
-
.argument("<folder>", "folder UUID or exact name", parseUuidOrSlugSelector)
|
|
405
|
-
.option("--include-resources", "include the published resources inside each folder")
|
|
406
|
-
.action(async function action(
|
|
407
|
-
this: Command,
|
|
408
|
-
folder: NormalizedUuidOrSlug,
|
|
409
|
-
commandOptions: FolderListCommandOptions,
|
|
410
|
-
) {
|
|
411
|
-
const { api, json } = createTeamspaceApi(this, filePath);
|
|
412
|
-
const resolvedFolder = await resolveFolderSelector(api, folder);
|
|
413
|
-
const allFolders = await api.listFolders(Boolean(commandOptions.includeResources));
|
|
414
|
-
const data = findFolderEntity(allFolders, resolvedFolder);
|
|
415
|
-
|
|
416
|
-
if (json) {
|
|
417
|
-
emitJson(stdout, "teamspace folders get", data, filePath);
|
|
418
|
-
return;
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
emitTable(stdout, data);
|
|
422
|
-
});
|
|
423
|
-
|
|
424
|
-
folders
|
|
425
|
-
.command("create")
|
|
426
|
-
.description("Create a teamspace folder")
|
|
427
|
-
.requiredOption("--name <name>", "folder name")
|
|
428
|
-
.option("--order <n>", "display order (lower values appear first)", parseNonNegativeInteger)
|
|
429
|
-
.option("--props <json>", "additionalProperties JSON: free-form client metadata, e.g. {\"icon\":\"folder-users\",\"color\":\"gold\"}")
|
|
430
|
-
.option("--props-file <path>", "path to a file with the additionalProperties JSON")
|
|
431
|
-
.action(async function action(
|
|
432
|
-
this: Command,
|
|
433
|
-
commandOptions: FolderCreateCommandOptions,
|
|
434
|
-
) {
|
|
435
|
-
const { api, json, readOnly } = createTeamspaceApi(this, filePath);
|
|
436
|
-
ensureWritable("teamspace folders create", readOnly, "write");
|
|
437
|
-
|
|
438
|
-
const data = await api.createFolder(buildFolderCreateInput(commandOptions));
|
|
439
|
-
|
|
440
|
-
if (json) {
|
|
441
|
-
emitJson(stdout, "teamspace folders create", data, filePath);
|
|
442
|
-
return;
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
writeLine(stdout, `Created folder "${getFolderIdentifier(data)}".`);
|
|
446
|
-
});
|
|
447
|
-
|
|
448
|
-
folders
|
|
449
|
-
.command("update")
|
|
450
|
-
.description("Update a teamspace folder")
|
|
451
|
-
.argument("<folder>", "folder UUID or exact name", parseUuidOrSlugSelector)
|
|
452
|
-
.option("--name <name>", "folder name")
|
|
453
|
-
.option("--order <n>", "display order (lower values appear first)", parseNonNegativeInteger)
|
|
454
|
-
.option("--props <json>", "additionalProperties JSON: free-form client metadata, e.g. {\"icon\":\"folder-users\",\"color\":\"gold\"}")
|
|
455
|
-
.option("--props-file <path>", "path to a file with the additionalProperties JSON")
|
|
456
|
-
.action(async function action(
|
|
457
|
-
this: Command,
|
|
458
|
-
folder: NormalizedUuidOrSlug,
|
|
459
|
-
commandOptions: FolderUpdateCommandOptions,
|
|
460
|
-
) {
|
|
461
|
-
const { api, json, readOnly } = createTeamspaceApi(this, filePath);
|
|
462
|
-
ensureWritable("teamspace folders update", readOnly, "write");
|
|
463
|
-
|
|
464
|
-
const resolvedFolder = await resolveFolderSelector(api, folder);
|
|
465
|
-
const data = await api.updateFolder(resolvedFolder, buildFolderUpdateInput(commandOptions));
|
|
466
|
-
|
|
467
|
-
if (json) {
|
|
468
|
-
emitJson(stdout, "teamspace folders update", data, filePath);
|
|
469
|
-
return;
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
writeLine(stdout, `Updated folder "${getFolderIdentifier(data)}".`);
|
|
473
|
-
});
|
|
474
|
-
|
|
475
|
-
folders
|
|
476
|
-
.command("delete")
|
|
477
|
-
.description("Delete a teamspace folder")
|
|
478
|
-
.argument("<folder>", "folder UUID or exact name", parseUuidOrSlugSelector)
|
|
479
|
-
.action(async function action(this: Command, folder: NormalizedUuidOrSlug) {
|
|
480
|
-
const { api, json, readOnly } = createTeamspaceApi(this, filePath);
|
|
481
|
-
ensureWritable("teamspace folders delete", readOnly, "destructive");
|
|
482
|
-
|
|
483
|
-
const resolvedFolder = await resolveFolderSelector(api, folder);
|
|
484
|
-
const data = await api.deleteFolder(resolvedFolder);
|
|
485
|
-
|
|
486
|
-
if (json) {
|
|
487
|
-
emitJson(stdout, "teamspace folders delete", data, filePath);
|
|
488
|
-
return;
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
writeLine(stdout, `Deleted folder "${resolvedFolder.value}".`);
|
|
492
|
-
});
|
|
493
|
-
|
|
494
|
-
const resources = teamspace.command("resources").description("Manage teamspace resources");
|
|
495
|
-
|
|
496
|
-
resources
|
|
497
|
-
.command("publish")
|
|
498
|
-
.description(
|
|
499
|
-
"Publish a resource to a teamspace folder. Publishing an already-published resource moves it to the new folder.",
|
|
500
|
-
)
|
|
501
|
-
.argument("<resource>", "schema UUID, schema slug, or display UUID", parseUuidOrSlugSelector)
|
|
502
|
-
.requiredOption(
|
|
503
|
-
"--to <folder>",
|
|
504
|
-
"target folder UUID or exact name (root folder UUID is 00000000-0000-0000-0000-000000000000)",
|
|
505
|
-
parseUuidOrSlugSelector,
|
|
506
|
-
)
|
|
507
|
-
.action(async function action(
|
|
508
|
-
this: Command,
|
|
509
|
-
resource: NormalizedUuidOrSlug,
|
|
510
|
-
commandOptions: ResourcePublishCommandOptions,
|
|
511
|
-
) {
|
|
512
|
-
const { api, json, readOnly } = createTeamspaceApi(this, filePath);
|
|
513
|
-
ensureWritable("teamspace resources publish", readOnly, "write");
|
|
514
|
-
|
|
515
|
-
const folder = await resolveFolderSelector(api, commandOptions.to);
|
|
516
|
-
const data = await api.publishResource({
|
|
517
|
-
resourceId: resource.value,
|
|
518
|
-
targetFolderId: folder.value,
|
|
519
|
-
});
|
|
520
|
-
|
|
521
|
-
if (json) {
|
|
522
|
-
emitJson(stdout, "teamspace resources publish", data, filePath);
|
|
523
|
-
return;
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
writeLine(stdout, `Published resource "${resource.value}" to folder "${folder.value}".`);
|
|
527
|
-
});
|
|
528
|
-
|
|
529
|
-
resources
|
|
530
|
-
.command("unpublish")
|
|
531
|
-
.description("Unpublish a resource from teamspace (removes it from its shared folder)")
|
|
532
|
-
.argument("<resource>", "schema UUID, schema slug, or display UUID", parseUuidOrSlugSelector)
|
|
533
|
-
.action(async function action(this: Command, resource: NormalizedUuidOrSlug) {
|
|
534
|
-
const { api, json, readOnly } = createTeamspaceApi(this, filePath);
|
|
535
|
-
ensureWritable("teamspace resources unpublish", readOnly, "write");
|
|
536
|
-
|
|
537
|
-
const data = await api.unpublishResource(resource.value);
|
|
538
|
-
|
|
539
|
-
if (json) {
|
|
540
|
-
emitJson(stdout, "teamspace resources unpublish", data, filePath);
|
|
541
|
-
return;
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
writeLine(stdout, `Unpublished resource "${resource.value}".`);
|
|
545
|
-
});
|
|
546
|
-
}
|
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "vitest";
|
|
2
|
-
|
|
3
|
-
import { buildProgram } from "../program.js";
|
|
4
|
-
import { getCompletions } from "./engine.js";
|
|
5
|
-
|
|
6
|
-
const NEVER_READ_PROFILES = (): readonly string[] => [];
|
|
7
|
-
|
|
8
|
-
describe("completion engine", () => {
|
|
9
|
-
it("suggests top-level subcommands and inherited globals", () => {
|
|
10
|
-
const program = buildProgram();
|
|
11
|
-
const result = getCompletions(
|
|
12
|
-
program,
|
|
13
|
-
{ words: ["zinkee", ""], cwordIndex: 1 },
|
|
14
|
-
{ readProfileNames: NEVER_READ_PROFILES },
|
|
15
|
-
);
|
|
16
|
-
|
|
17
|
-
expect(result).toContain("schemas");
|
|
18
|
-
expect(result).toContain("automations");
|
|
19
|
-
expect(result).toContain("--json");
|
|
20
|
-
expect(result).toContain("--profile");
|
|
21
|
-
expect(result).toContain("--help");
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
it("filters subcommands by prefix", () => {
|
|
25
|
-
const program = buildProgram();
|
|
26
|
-
const result = getCompletions(
|
|
27
|
-
program,
|
|
28
|
-
{ words: ["zinkee", "sch"], cwordIndex: 1 },
|
|
29
|
-
{ readProfileNames: NEVER_READ_PROFILES },
|
|
30
|
-
);
|
|
31
|
-
|
|
32
|
-
expect(result).toEqual(["schemas"]);
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
it("descends into subcommands", () => {
|
|
36
|
-
const program = buildProgram();
|
|
37
|
-
const result = getCompletions(
|
|
38
|
-
program,
|
|
39
|
-
{ words: ["zinkee", "schemas", ""], cwordIndex: 2 },
|
|
40
|
-
{ readProfileNames: NEVER_READ_PROFILES },
|
|
41
|
-
);
|
|
42
|
-
|
|
43
|
-
expect(result).toContain("list");
|
|
44
|
-
expect(result).toContain("get");
|
|
45
|
-
expect(result).toContain("create");
|
|
46
|
-
expect(result).toContain("fields");
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
it("descends into nested subcommands", () => {
|
|
50
|
-
const program = buildProgram();
|
|
51
|
-
const result = getCompletions(
|
|
52
|
-
program,
|
|
53
|
-
{ words: ["zinkee", "schemas", "fields", ""], cwordIndex: 3 },
|
|
54
|
-
{ readProfileNames: NEVER_READ_PROFILES },
|
|
55
|
-
);
|
|
56
|
-
|
|
57
|
-
expect(result).toContain("list");
|
|
58
|
-
expect(result).toContain("create");
|
|
59
|
-
expect(result).toContain("update");
|
|
60
|
-
expect(result).toContain("delete");
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
it("only suggests long flags when the current token starts with --", () => {
|
|
64
|
-
const program = buildProgram();
|
|
65
|
-
const result = getCompletions(
|
|
66
|
-
program,
|
|
67
|
-
{ words: ["zinkee", "schemas", "list", "--"], cwordIndex: 3 },
|
|
68
|
-
{ readProfileNames: NEVER_READ_PROFILES },
|
|
69
|
-
);
|
|
70
|
-
|
|
71
|
-
expect(result.every((candidate) => candidate.startsWith("--"))).toBe(true);
|
|
72
|
-
expect(result).toContain("--json");
|
|
73
|
-
expect(result).toContain("--profile");
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
it("skips flag tokens when descending into the command tree", () => {
|
|
77
|
-
const program = buildProgram();
|
|
78
|
-
const result = getCompletions(
|
|
79
|
-
program,
|
|
80
|
-
{
|
|
81
|
-
words: ["zinkee", "--profile", "prod", "schemas", ""],
|
|
82
|
-
cwordIndex: 4,
|
|
83
|
-
},
|
|
84
|
-
{ readProfileNames: NEVER_READ_PROFILES },
|
|
85
|
-
);
|
|
86
|
-
|
|
87
|
-
expect(result).toContain("list");
|
|
88
|
-
expect(result).toContain("fields");
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
it("returns profile names when the previous token is --profile", () => {
|
|
92
|
-
const program = buildProgram();
|
|
93
|
-
const result = getCompletions(
|
|
94
|
-
program,
|
|
95
|
-
{ words: ["zinkee", "--profile", ""], cwordIndex: 2 },
|
|
96
|
-
{ readProfileNames: () => ["alpha", "beta"] },
|
|
97
|
-
);
|
|
98
|
-
|
|
99
|
-
expect(result).toEqual(["alpha", "beta"]);
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
it("filters profile names by prefix", () => {
|
|
103
|
-
const program = buildProgram();
|
|
104
|
-
const result = getCompletions(
|
|
105
|
-
program,
|
|
106
|
-
{ words: ["zinkee", "--profile", "be"], cwordIndex: 2 },
|
|
107
|
-
{ readProfileNames: () => ["alpha", "beta"] },
|
|
108
|
-
);
|
|
109
|
-
|
|
110
|
-
expect(result).toEqual(["beta"]);
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
it("does not crash when profile reader throws", () => {
|
|
114
|
-
const program = buildProgram();
|
|
115
|
-
const result = getCompletions(
|
|
116
|
-
program,
|
|
117
|
-
{ words: ["zinkee", "--profile", ""], cwordIndex: 2 },
|
|
118
|
-
{
|
|
119
|
-
readProfileNames: () => {
|
|
120
|
-
throw new Error("config unreadable");
|
|
121
|
-
},
|
|
122
|
-
},
|
|
123
|
-
);
|
|
124
|
-
|
|
125
|
-
expect(result).toEqual([]);
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
it("ignores hidden and unknown subcommands", () => {
|
|
129
|
-
const program = buildProgram();
|
|
130
|
-
const result = getCompletions(
|
|
131
|
-
program,
|
|
132
|
-
{ words: ["zinkee", "no-such-thing", ""], cwordIndex: 2 },
|
|
133
|
-
{ readProfileNames: NEVER_READ_PROFILES },
|
|
134
|
-
);
|
|
135
|
-
|
|
136
|
-
expect(result).toContain("schemas");
|
|
137
|
-
});
|
|
138
|
-
});
|