@gonovus-ai/spec-flow-cli 0.0.0-stage → 0.2.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/README.md +807 -2
- package/dist/authentication-command-topology.d.ts +7 -0
- package/dist/authentication-command-topology.js +9 -0
- package/dist/authentication-command-topology.js.map +1 -0
- package/dist/bin.d.ts +2 -0
- package/dist/bin.js +52 -0
- package/dist/bin.js.map +1 -0
- package/dist/cli.d.ts +16 -0
- package/dist/cli.js +3339 -0
- package/dist/cli.js.map +1 -0
- package/dist/client-identity.d.ts +12 -0
- package/dist/client-identity.js +29 -0
- package/dist/client-identity.js.map +1 -0
- package/dist/credential-store.d.ts +20 -0
- package/dist/credential-store.js +146 -0
- package/dist/credential-store.js.map +1 -0
- package/dist/default-api-origin.d.ts +1 -0
- package/dist/default-api-origin.js +1 -0
- package/dist/default-api-origin.js.map +1 -0
- package/dist/github-account-commands.d.ts +25 -0
- package/dist/github-account-commands.js +117 -0
- package/dist/github-account-commands.js.map +1 -0
- package/dist/github-connection-commands.d.ts +4 -0
- package/dist/github-connection-commands.js +95 -0
- package/dist/github-connection-commands.js.map +1 -0
- package/dist/github-import-commands.d.ts +3 -0
- package/dist/github-import-commands.js +59 -0
- package/dist/github-import-commands.js.map +1 -0
- package/dist/http-origin.d.ts +13 -0
- package/dist/http-origin.js +39 -0
- package/dist/http-origin.js.map +1 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.js +238 -0
- package/dist/index.js.map +1 -0
- package/dist/node-version.d.ts +1 -0
- package/dist/node-version.js +8 -0
- package/dist/node-version.js.map +1 -0
- package/dist/safe-cli-error.d.ts +2 -0
- package/dist/safe-cli-error.js +3 -0
- package/dist/safe-cli-error.js.map +1 -0
- package/dist/user-agent.d.ts +3 -0
- package/dist/user-agent.js +10 -0
- package/dist/user-agent.js.map +1 -0
- package/dist/user-api-key-commands.d.ts +29 -0
- package/dist/user-api-key-commands.js +128 -0
- package/dist/user-api-key-commands.js.map +1 -0
- package/dist/work-item-asset-commands.d.ts +24 -0
- package/dist/work-item-asset-commands.js +211 -0
- package/dist/work-item-asset-commands.js.map +1 -0
- package/dist/work-item-exchange-file.d.ts +4 -0
- package/dist/work-item-exchange-file.js +66 -0
- package/dist/work-item-exchange-file.js.map +1 -0
- package/dist/work-item-resource-link-commands.d.ts +23 -0
- package/dist/work-item-resource-link-commands.js +85 -0
- package/dist/work-item-resource-link-commands.js.map +1 -0
- package/dist/work-item-time-entry-input.d.ts +4 -0
- package/dist/work-item-time-entry-input.js +27 -0
- package/dist/work-item-time-entry-input.js.map +1 -0
- package/package.json +53 -3
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { InvalidArgumentError } from "commander";
|
|
2
|
+
import { open } from "node:fs/promises";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
const uuid = (value) => {
|
|
5
|
+
if (!z.uuid().safeParse(value).success)
|
|
6
|
+
throw new InvalidArgumentError("Asset and command identities must be UUIDs");
|
|
7
|
+
return value;
|
|
8
|
+
};
|
|
9
|
+
const maximumBytes = 10 * 1024 * 1024;
|
|
10
|
+
const reservationSchema = z.object({
|
|
11
|
+
uploadId: z.uuid(),
|
|
12
|
+
uploadUrl: z.url(),
|
|
13
|
+
expiresAt: z.iso.datetime(),
|
|
14
|
+
headers: z.object({ "Content-Type": z.string().max(127) }).strict(),
|
|
15
|
+
});
|
|
16
|
+
/** One published CLI entry with explicit replayable upload phases. The signed
|
|
17
|
+
* reservation is read from a file rather than repeated in shell arguments.
|
|
18
|
+
*/
|
|
19
|
+
export function registerWorkItemAssetCommands(workItem, runtime) {
|
|
20
|
+
const commands = workItem
|
|
21
|
+
.command("asset")
|
|
22
|
+
.description("Upload and read private task images and attachments");
|
|
23
|
+
const retry = {
|
|
24
|
+
retryTransportOnce: true,
|
|
25
|
+
ambiguousOutcomeMessage: "Asset command outcome is unknown; retry the same phase with its original inputs and command key",
|
|
26
|
+
};
|
|
27
|
+
const writeAllowed = () => runtime.dependencies.environment.SPEC_FLOW_PROJECT_READ_TOKEN === undefined;
|
|
28
|
+
commands
|
|
29
|
+
.command("reserve")
|
|
30
|
+
.requiredOption("--project-id <id>")
|
|
31
|
+
.requiredOption("--work-item-id <id>")
|
|
32
|
+
.requiredOption("--filename <name>")
|
|
33
|
+
.requiredOption("--content-type <type>")
|
|
34
|
+
.requiredOption("--size <bytes>", "Declared byte length", (value) => {
|
|
35
|
+
if (!/^[1-9][0-9]*$/u.test(value) || Number(value) > 2_147_483_647)
|
|
36
|
+
throw new InvalidArgumentError("Asset size must be a positive bounded integer");
|
|
37
|
+
return Number(value);
|
|
38
|
+
})
|
|
39
|
+
.requiredOption("--surface <surface>", "description or comment", (value) => {
|
|
40
|
+
if (value !== "description" && value !== "comment")
|
|
41
|
+
throw new InvalidArgumentError("Asset surface must be description or comment");
|
|
42
|
+
return value;
|
|
43
|
+
})
|
|
44
|
+
.requiredOption("--idempotency-key <key>", "Keep this key when retrying reservation", uuid)
|
|
45
|
+
.action(async (options) => {
|
|
46
|
+
if (!writeAllowed()) {
|
|
47
|
+
runtime.setExitCode(runtime.rejectWrite());
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
runtime.setExitCode(await runtime.run((client) => client.POST("/v1/projects/{projectId}/work-items/{workItemId}/assets/uploads", {
|
|
51
|
+
body: {
|
|
52
|
+
filename: options.filename,
|
|
53
|
+
contentType: options.contentType,
|
|
54
|
+
size: options.size,
|
|
55
|
+
surface: options.surface,
|
|
56
|
+
},
|
|
57
|
+
params: {
|
|
58
|
+
path: options,
|
|
59
|
+
header: { "idempotency-key": options.idempotencyKey },
|
|
60
|
+
},
|
|
61
|
+
}), retry));
|
|
62
|
+
});
|
|
63
|
+
commands
|
|
64
|
+
.command("put")
|
|
65
|
+
.description("Upload bytes using reserve JSON saved to an owner-only file; this does not link content")
|
|
66
|
+
.requiredOption("--reservation-file <path>")
|
|
67
|
+
.requiredOption("--file <path>")
|
|
68
|
+
.action(async (options) => {
|
|
69
|
+
if (!writeAllowed()) {
|
|
70
|
+
runtime.setExitCode(runtime.rejectWrite());
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
runtime.setExitCode(await runtime.run(async () => {
|
|
74
|
+
const source = await readBoundedFile(options.reservationFile, 32_768);
|
|
75
|
+
const reservation = reservationSchema.parse(JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(source)));
|
|
76
|
+
const url = new URL(reservation.uploadUrl);
|
|
77
|
+
if ((url.protocol !== "http:" && url.protocol !== "https:") ||
|
|
78
|
+
url.username !== "" ||
|
|
79
|
+
url.password !== "" ||
|
|
80
|
+
url.hash !== "" ||
|
|
81
|
+
new Date(reservation.expiresAt).getTime() <= Date.now())
|
|
82
|
+
throw new Error("Upload reservation is invalid or expired");
|
|
83
|
+
const bytes = await readBoundedFile(options.file, maximumBytes);
|
|
84
|
+
const request = () => new Request(url, {
|
|
85
|
+
method: "PUT",
|
|
86
|
+
headers: reservation.headers,
|
|
87
|
+
body: new Uint8Array(bytes),
|
|
88
|
+
redirect: "error",
|
|
89
|
+
credentials: "omit",
|
|
90
|
+
signal: AbortSignal.timeout(60_000),
|
|
91
|
+
});
|
|
92
|
+
let response;
|
|
93
|
+
try {
|
|
94
|
+
response = await runtime.dependencies.fetch(request());
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
response = await runtime.dependencies.fetch(request());
|
|
98
|
+
}
|
|
99
|
+
await response.body?.cancel();
|
|
100
|
+
return {
|
|
101
|
+
response,
|
|
102
|
+
...(response.ok
|
|
103
|
+
? { data: { uploadId: reservation.uploadId, uploaded: true } }
|
|
104
|
+
: {
|
|
105
|
+
error: {
|
|
106
|
+
code: "WORK_ITEM_ASSET_UPLOAD_FAILED",
|
|
107
|
+
message: "Asset upload failed; retry the same reservation and file before its expiration",
|
|
108
|
+
},
|
|
109
|
+
}),
|
|
110
|
+
};
|
|
111
|
+
}, retry));
|
|
112
|
+
});
|
|
113
|
+
commands
|
|
114
|
+
.command("finalize")
|
|
115
|
+
.requiredOption("--project-id <id>")
|
|
116
|
+
.requiredOption("--work-item-id <id>")
|
|
117
|
+
.requiredOption("--upload-id <id>", "Server-issued upload UUID", uuid)
|
|
118
|
+
.requiredOption("--idempotency-key <key>", "Keep this key when retrying finalization", uuid)
|
|
119
|
+
.action(async (options) => {
|
|
120
|
+
if (!writeAllowed()) {
|
|
121
|
+
runtime.setExitCode(runtime.rejectWrite());
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
runtime.setExitCode(await runtime.run((client) => client.POST("/v1/projects/{projectId}/work-items/{workItemId}/assets/finalize", {
|
|
125
|
+
body: { uploadId: options.uploadId },
|
|
126
|
+
params: {
|
|
127
|
+
path: options,
|
|
128
|
+
header: { "idempotency-key": options.idempotencyKey },
|
|
129
|
+
},
|
|
130
|
+
}), retry));
|
|
131
|
+
});
|
|
132
|
+
commands
|
|
133
|
+
.command("get")
|
|
134
|
+
.requiredOption("--project-id <id>")
|
|
135
|
+
.requiredOption("--asset-id <id>", "Asset UUID", uuid)
|
|
136
|
+
.action(async (options) => {
|
|
137
|
+
runtime.setExitCode(await runtime.run((client) => client.GET("/v1/work-item-assets/{assetId}/metadata", {
|
|
138
|
+
params: {
|
|
139
|
+
path: { assetId: options.assetId },
|
|
140
|
+
query: { projectId: options.projectId },
|
|
141
|
+
},
|
|
142
|
+
})));
|
|
143
|
+
});
|
|
144
|
+
commands
|
|
145
|
+
.command("download")
|
|
146
|
+
.requiredOption("--project-id <id>")
|
|
147
|
+
.requiredOption("--asset-id <id>", "Asset UUID", uuid)
|
|
148
|
+
.requiredOption("--output-file <path>", "Create a new owner-only file; existing files are never overwritten")
|
|
149
|
+
.action(async (options) => {
|
|
150
|
+
runtime.setExitCode(await runtime.run(async (client) => {
|
|
151
|
+
const params = {
|
|
152
|
+
path: { assetId: options.assetId },
|
|
153
|
+
query: { projectId: options.projectId },
|
|
154
|
+
};
|
|
155
|
+
const metadata = await client.GET("/v1/work-item-assets/{assetId}/metadata", { params });
|
|
156
|
+
if (metadata.data === undefined)
|
|
157
|
+
return metadata;
|
|
158
|
+
if (metadata.data.asset.size > maximumBytes)
|
|
159
|
+
return {
|
|
160
|
+
response: new Response(null, { status: 400 }),
|
|
161
|
+
error: { message: "Asset exceeds the CLI download size limit" },
|
|
162
|
+
};
|
|
163
|
+
const content = await client.GET("/v1/work-item-assets/{assetId}", {
|
|
164
|
+
params,
|
|
165
|
+
parseAs: "arrayBuffer",
|
|
166
|
+
});
|
|
167
|
+
if (content.data === undefined)
|
|
168
|
+
return content;
|
|
169
|
+
if (content.data.byteLength !== metadata.data.asset.size)
|
|
170
|
+
throw new Error("Asset download length did not match its verified metadata");
|
|
171
|
+
const file = await open(options.outputFile, "wx", 0o600);
|
|
172
|
+
try {
|
|
173
|
+
await file.writeFile(new Uint8Array(content.data));
|
|
174
|
+
}
|
|
175
|
+
finally {
|
|
176
|
+
await file.close();
|
|
177
|
+
}
|
|
178
|
+
return {
|
|
179
|
+
response: content.response,
|
|
180
|
+
data: {
|
|
181
|
+
assetId: options.assetId,
|
|
182
|
+
filename: metadata.data.asset.filename,
|
|
183
|
+
size: content.data.byteLength,
|
|
184
|
+
outputFile: options.outputFile,
|
|
185
|
+
},
|
|
186
|
+
};
|
|
187
|
+
}));
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
async function readBoundedFile(path, maximum) {
|
|
191
|
+
const file = await open(path, "r");
|
|
192
|
+
try {
|
|
193
|
+
if (!(await file.stat()).isFile())
|
|
194
|
+
throw new Error("Asset input must be a regular file");
|
|
195
|
+
const bytes = Buffer.alloc(maximum + 1);
|
|
196
|
+
let offset = 0;
|
|
197
|
+
while (offset < bytes.length) {
|
|
198
|
+
const read = await file.read(bytes, offset, bytes.length - offset, offset);
|
|
199
|
+
if (read.bytesRead === 0)
|
|
200
|
+
break;
|
|
201
|
+
offset += read.bytesRead;
|
|
202
|
+
}
|
|
203
|
+
if (offset === 0 || offset > maximum)
|
|
204
|
+
throw new Error("Asset input is empty or exceeds its allowed size");
|
|
205
|
+
return bytes.subarray(0, offset);
|
|
206
|
+
}
|
|
207
|
+
finally {
|
|
208
|
+
await file.close();
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
//# sourceMappingURL=work-item-asset-commands.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"work-item-asset-commands.js","sourceRoot":"","sources":["../src/work-item-asset-commands.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,oBAAoB,EAAE,MAAM,WAAW,CAAC;AACjD,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAExC,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AA4BxB,MAAM,IAAI,GAAG,CAAC,KAAa,EAAU,EAAE;IACrC,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,OAAO;QACpC,MAAM,IAAI,oBAAoB,CAC5B,4CAA4C,CAC7C,CAAC;IACJ,OAAO,KAAK,CAAC;AACf,CAAC,CAAC;AACF,MAAM,YAAY,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI,CAAC;AACtC,MAAM,iBAAiB,GAAG,CAAC,CAAC,MAAM,CAAC;IACjC,QAAQ,EAAE,CAAC,CAAC,IAAI,EAAE;IAClB,SAAS,EAAE,CAAC,CAAC,GAAG,EAAE;IAClB,SAAS,EAAE,CAAC,CAAC,GAAG,CAAC,QAAQ,EAAE;IAC3B,OAAO,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,cAAc,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,MAAM,EAAE;CACpE,CAAC,CAAC;AAEH;;GAEG;AACH,MAAM,UAAU,6BAA6B,CAC3C,QAAiB,EACjB,OAAgB;IAEhB,MAAM,QAAQ,GAAG,QAAQ;SACtB,OAAO,CAAC,OAAO,CAAC;SAChB,WAAW,CAAC,qDAAqD,CAAC,CAAC;IACtE,MAAM,KAAK,GAAG;QACZ,kBAAkB,EAAE,IAAI;QACxB,uBAAuB,EACrB,iGAAiG;KACpG,CAAC;IACF,MAAM,YAAY,GAAG,GAAG,EAAE,CACxB,OAAO,CAAC,YAAY,CAAC,WAAW,CAAC,4BAA4B,KAAK,SAAS,CAAC;IAC9E,QAAQ;SACL,OAAO,CAAC,SAAS,CAAC;SAClB,cAAc,CAAC,mBAAmB,CAAC;SACnC,cAAc,CAAC,qBAAqB,CAAC;SACrC,cAAc,CAAC,mBAAmB,CAAC;SACnC,cAAc,CAAC,uBAAuB,CAAC;SACvC,cAAc,CACb,gBAAgB,EAChB,sBAAsB,EACtB,CAAC,KAAa,EAAE,EAAE;QAChB,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,MAAM,CAAC,KAAK,CAAC,GAAG,aAAa;YAChE,MAAM,IAAI,oBAAoB,CAC5B,+CAA+C,CAChD,CAAC;QACJ,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;IACvB,CAAC,CACF;SACA,cAAc,CACb,qBAAqB,EACrB,wBAAwB,EACxB,CAAC,KAAa,EAAE,EAAE;QAChB,IAAI,KAAK,KAAK,aAAa,IAAI,KAAK,KAAK,SAAS;YAChD,MAAM,IAAI,oBAAoB,CAC5B,8CAA8C,CAC/C,CAAC;QACJ,OAAO,KAAK,CAAC;IACf,CAAC,CACF;SACA,cAAc,CACb,yBAAyB,EACzB,yCAAyC,EACzC,IAAI,CACL;SACA,MAAM,CACL,KAAK,EACH,OAMC,EACD,EAAE;QACF,IAAI,CAAC,YAAY,EAAE,EAAE,CAAC;YACpB,OAAO,CAAC,WAAW,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC;YAC3C,OAAO;QACT,CAAC;QACD,OAAO,CAAC,WAAW,CACjB,MAAM,OAAO,CAAC,GAAG,CACf,CAAC,MAAM,EAAE,EAAE,CACT,MAAM,CAAC,IAAI,CACT,iEAAiE,EACjE;YACE,IAAI,EAAE;gBACJ,QAAQ,EAAE,OAAO,CAAC,QAAQ;gBAC1B,WAAW,EAAE,OAAO,CAAC,WAAW;gBAChC,IAAI,EAAE,OAAO,CAAC,IAAI;gBAClB,OAAO,EAAE,OAAO,CAAC,OAAO;aACzB;YACD,MAAM,EAAE;gBACN,IAAI,EAAE,OAAO;gBACb,MAAM,EAAE,EAAE,iBAAiB,EAAE,OAAO,CAAC,cAAc,EAAE;aACtD;SACF,CACF,EACH,KAAK,CACN,CACF,CAAC;IACJ,CAAC,CACF,CAAC;IACJ,QAAQ;SACL,OAAO,CAAC,KAAK,CAAC;SACd,WAAW,CACV,yFAAyF,CAC1F;SACA,cAAc,CAAC,2BAA2B,CAAC;SAC3C,cAAc,CAAC,eAAe,CAAC;SAC/B,MAAM,CACL,KAAK,EAAE,OAGN,EAAE,EAAE;QACH,IAAI,CAAC,YAAY,EAAE,EAAE,CAAC;YACpB,OAAO,CAAC,WAAW,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC;YAC3C,OAAO;QACT,CAAC;QACD,OAAO,CAAC,WAAW,CACjB,MAAM,OAAO,CAAC,GAAG,CAAC,KAAK,IAAI,EAAE;YAC3B,MAAM,MAAM,GAAG,MAAM,eAAe,CAClC,OAAO,CAAC,eAAe,EACvB,MAAM,CACP,CAAC;YACF,MAAM,WAAW,GAAG,iBAAiB,CAAC,KAAK,CACzC,IAAI,CAAC,KAAK,CACR,IAAI,WAAW,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAC9C,CACb,CAAC;YACF,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,WAAW,CAAC,SAAS,CAAC,CAAC;YAC3C,IACE,CAAC,GAAG,CAAC,QAAQ,KAAK,OAAO,IAAI,GAAG,CAAC,QAAQ,KAAK,QAAQ,CAAC;gBACvD,GAAG,CAAC,QAAQ,KAAK,EAAE;gBACnB,GAAG,CAAC,QAAQ,KAAK,EAAE;gBACnB,GAAG,CAAC,IAAI,KAAK,EAAE;gBACf,IAAI,IAAI,CAAC,WAAW,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE,IAAI,IAAI,CAAC,GAAG,EAAE;gBAEvD,MAAM,IAAI,KAAK,CAAC,0CAA0C,CAAC,CAAC;YAC9D,MAAM,KAAK,GAAG,MAAM,eAAe,CAAC,OAAO,CAAC,IAAI,EAAE,YAAY,CAAC,CAAC;YAChE,MAAM,OAAO,GAAG,GAAG,EAAE,CACnB,IAAI,OAAO,CAAC,GAAG,EAAE;gBACf,MAAM,EAAE,KAAK;gBACb,OAAO,EAAE,WAAW,CAAC,OAAO;gBAC5B,IAAI,EAAE,IAAI,UAAU,CAAC,KAAK,CAAC;gBAC3B,QAAQ,EAAE,OAAO;gBACjB,WAAW,EAAE,MAAM;gBACnB,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,MAAM,CAAC;aACpC,CAAC,CAAC;YACL,IAAI,QAAkB,CAAC;YACvB,IAAI,CAAC;gBACH,QAAQ,GAAG,MAAM,OAAO,CAAC,YAAY,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;YACzD,CAAC;YAAC,MAAM,CAAC;gBACP,QAAQ,GAAG,MAAM,OAAO,CAAC,YAAY,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;YACzD,CAAC;YACD,MAAM,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC;YAC9B,OAAO;gBACL,QAAQ;gBACR,GAAG,CAAC,QAAQ,CAAC,EAAE;oBACb,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,QAAQ,EAAE,WAAW,CAAC,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE;oBAC9D,CAAC,CAAC;wBACE,KAAK,EAAE;4BACL,IAAI,EAAE,+BAA+B;4BACrC,OAAO,EACL,gFAAgF;yBACnF;qBACF,CAAC;aACP,CAAC;QACJ,CAAC,EAAE,KAAK,CAAC,CACV,CAAC;IACJ,CAAC,CACF,CAAC;IACJ,QAAQ;SACL,OAAO,CAAC,UAAU,CAAC;SACnB,cAAc,CAAC,mBAAmB,CAAC;SACnC,cAAc,CAAC,qBAAqB,CAAC;SACrC,cAAc,CAAC,kBAAkB,EAAE,2BAA2B,EAAE,IAAI,CAAC;SACrE,cAAc,CACb,yBAAyB,EACzB,0CAA0C,EAC1C,IAAI,CACL;SACA,MAAM,CACL,KAAK,EACH,OAGC,EACD,EAAE;QACF,IAAI,CAAC,YAAY,EAAE,EAAE,CAAC;YACpB,OAAO,CAAC,WAAW,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC;YAC3C,OAAO;QACT,CAAC;QACD,OAAO,CAAC,WAAW,CACjB,MAAM,OAAO,CAAC,GAAG,CACf,CAAC,MAAM,EAAE,EAAE,CACT,MAAM,CAAC,IAAI,CACT,kEAAkE,EAClE;YACE,IAAI,EAAE,EAAE,QAAQ,EAAE,OAAO,CAAC,QAAQ,EAAE;YACpC,MAAM,EAAE;gBACN,IAAI,EAAE,OAAO;gBACb,MAAM,EAAE,EAAE,iBAAiB,EAAE,OAAO,CAAC,cAAc,EAAE;aACtD;SACF,CACF,EACH,KAAK,CACN,CACF,CAAC;IACJ,CAAC,CACF,CAAC;IACJ,QAAQ;SACL,OAAO,CAAC,KAAK,CAAC;SACd,cAAc,CAAC,mBAAmB,CAAC;SACnC,cAAc,CAAC,iBAAiB,EAAE,YAAY,EAAE,IAAI,CAAC;SACrD,MAAM,CACL,KAAK,EAAE,OAGN,EAAE,EAAE;QACH,OAAO,CAAC,WAAW,CACjB,MAAM,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAC3B,MAAM,CAAC,GAAG,CAAC,yCAAyC,EAAE;YACpD,MAAM,EAAE;gBACN,IAAI,EAAE,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE;gBAClC,KAAK,EAAE,EAAE,SAAS,EAAE,OAAO,CAAC,SAAS,EAAE;aACxC;SACF,CAAC,CACH,CACF,CAAC;IACJ,CAAC,CACF,CAAC;IACJ,QAAQ;SACL,OAAO,CAAC,UAAU,CAAC;SACnB,cAAc,CAAC,mBAAmB,CAAC;SACnC,cAAc,CAAC,iBAAiB,EAAE,YAAY,EAAE,IAAI,CAAC;SACrD,cAAc,CACb,sBAAsB,EACtB,oEAAoE,CACrE;SACA,MAAM,CACL,KAAK,EAAE,OAIN,EAAE,EAAE;QACH,OAAO,CAAC,WAAW,CACjB,MAAM,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE;YACjC,MAAM,MAAM,GAAG;gBACb,IAAI,EAAE,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE;gBAClC,KAAK,EAAE,EAAE,SAAS,EAAE,OAAO,CAAC,SAAS,EAAE;aACxC,CAAC;YACF,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,GAAG,CAC/B,yCAAyC,EACzC,EAAE,MAAM,EAAE,CACX,CAAC;YACF,IAAI,QAAQ,CAAC,IAAI,KAAK,SAAS;gBAAE,OAAO,QAAQ,CAAC;YACjD,IAAI,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,GAAG,YAAY;gBACzC,OAAO;oBACL,QAAQ,EAAE,IAAI,QAAQ,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC;oBAC7C,KAAK,EAAE,EAAE,OAAO,EAAE,2CAA2C,EAAE;iBAChE,CAAC;YACJ,MAAM,OAAO,GAAG,MAAM,MAAM,CAAC,GAAG,CAAC,gCAAgC,EAAE;gBACjE,MAAM;gBACN,OAAO,EAAE,aAAa;aACvB,CAAC,CAAC;YACH,IAAI,OAAO,CAAC,IAAI,KAAK,SAAS;gBAAE,OAAO,OAAO,CAAC;YAC/C,IAAI,OAAO,CAAC,IAAI,CAAC,UAAU,KAAK,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI;gBACtD,MAAM,IAAI,KAAK,CACb,2DAA2D,CAC5D,CAAC;YACJ,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC;YACzD,IAAI,CAAC;gBACH,MAAM,IAAI,CAAC,SAAS,CAAC,IAAI,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;YACrD,CAAC;oBAAS,CAAC;gBACT,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC;YACrB,CAAC;YACD,OAAO;gBACL,QAAQ,EAAE,OAAO,CAAC,QAAQ;gBAC1B,IAAI,EAAE;oBACJ,OAAO,EAAE,OAAO,CAAC,OAAO;oBACxB,QAAQ,EAAE,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ;oBACtC,IAAI,EAAE,OAAO,CAAC,IAAI,CAAC,UAAU;oBAC7B,UAAU,EAAE,OAAO,CAAC,UAAU;iBAC/B;aACF,CAAC;QACJ,CAAC,CAAC,CACH,CAAC;IACJ,CAAC,CACF,CAAC;AACN,CAAC;AAED,KAAK,UAAU,eAAe,CAC5B,IAAY,EACZ,OAAe;IAEf,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IACnC,IAAI,CAAC;QACH,IAAI,CAAC,CAAC,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;YAC/B,MAAM,IAAI,KAAK,CAAC,oCAAoC,CAAC,CAAC;QACxD,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,OAAO,GAAG,CAAC,CAAC,CAAC;QACxC,IAAI,MAAM,GAAG,CAAC,CAAC;QACf,OAAO,MAAM,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC;YAC7B,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,IAAI,CAC1B,KAAK,EACL,MAAM,EACN,KAAK,CAAC,MAAM,GAAG,MAAM,EACrB,MAAM,CACP,CAAC;YACF,IAAI,IAAI,CAAC,SAAS,KAAK,CAAC;gBAAE,MAAM;YAChC,MAAM,IAAI,IAAI,CAAC,SAAS,CAAC;QAC3B,CAAC;QACD,IAAI,MAAM,KAAK,CAAC,IAAI,MAAM,GAAG,OAAO;YAClC,MAAM,IAAI,KAAK,CAAC,kDAAkD,CAAC,CAAC;QACtE,OAAO,KAAK,CAAC,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;IACnC,CAAC;YAAS,CAAC;QACT,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC;IACrB,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { paths } from "@spec-flow/api-client";
|
|
2
|
+
type ImportBody = NonNullable<paths["/v1/projects/{projectId}/work-items/import"]["post"]["requestBody"]>["content"]["application/json"];
|
|
3
|
+
export declare function readWorkItemImportFile(path: string): Promise<ImportBody | null>;
|
|
4
|
+
export {};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { open } from "node:fs/promises";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
// Mirrors the generated import request bounds. Project metadata in an export
|
|
4
|
+
// is descriptive only; the explicit command Project selects the destination.
|
|
5
|
+
const maximumBytes = 1_048_576;
|
|
6
|
+
const documentSchema = z.object({
|
|
7
|
+
project: z.unknown().optional(),
|
|
8
|
+
tasks: z.array(z.object({
|
|
9
|
+
title: z.string().max(300),
|
|
10
|
+
description: z.string().max(20_000).optional(),
|
|
11
|
+
status: z.string().max(200),
|
|
12
|
+
priority: z.string().max(200).optional(),
|
|
13
|
+
startDate: z.string().max(40).nullable().optional(),
|
|
14
|
+
dueDate: z.string().max(40).nullable().optional(),
|
|
15
|
+
userId: z.string().max(200).nullable().optional(),
|
|
16
|
+
labels: z.array(z.object({
|
|
17
|
+
name: z.string().max(120),
|
|
18
|
+
color: z.string().max(100),
|
|
19
|
+
}).strict()).max(1000).optional(),
|
|
20
|
+
}).strict()).min(1).max(1000),
|
|
21
|
+
}).strict();
|
|
22
|
+
export async function readWorkItemImportFile(path) {
|
|
23
|
+
try {
|
|
24
|
+
const file = await open(path, "r");
|
|
25
|
+
try {
|
|
26
|
+
if (!(await file.stat()).isFile()) {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
// Bound the actual read, including when the file changes after stat.
|
|
30
|
+
const buffer = Buffer.alloc(maximumBytes + 1);
|
|
31
|
+
let length = 0;
|
|
32
|
+
while (length < buffer.length) {
|
|
33
|
+
const { bytesRead } = await file.read(buffer, length, buffer.length - length, length);
|
|
34
|
+
if (bytesRead === 0) {
|
|
35
|
+
break;
|
|
36
|
+
}
|
|
37
|
+
length += bytesRead;
|
|
38
|
+
}
|
|
39
|
+
if (length > maximumBytes) {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
const source = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(buffer.subarray(0, length)));
|
|
43
|
+
const parsed = documentSchema.safeParse(source);
|
|
44
|
+
if (!parsed.success) {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
return { tasks: parsed.data.tasks.map((task) => ({
|
|
48
|
+
title: task.title,
|
|
49
|
+
status: task.status,
|
|
50
|
+
...(task.description === undefined ? {} : { description: task.description }),
|
|
51
|
+
...(task.priority === undefined ? {} : { priority: task.priority }),
|
|
52
|
+
...(task.startDate === undefined ? {} : { startDate: task.startDate }),
|
|
53
|
+
...(task.dueDate === undefined ? {} : { dueDate: task.dueDate }),
|
|
54
|
+
...(task.userId === undefined ? {} : { userId: task.userId }),
|
|
55
|
+
...(task.labels === undefined ? {} : { labels: task.labels }),
|
|
56
|
+
})) };
|
|
57
|
+
}
|
|
58
|
+
finally {
|
|
59
|
+
await file.close();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=work-item-exchange-file.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"work-item-exchange-file.js","sourceRoot":"","sources":["../src/work-item-exchange-file.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type Command } from "commander";
|
|
2
|
+
import type createClient from "openapi-fetch";
|
|
3
|
+
import type { paths } from "@spec-flow/api-client";
|
|
4
|
+
type Client = ReturnType<typeof createClient<paths>>;
|
|
5
|
+
interface Runtime {
|
|
6
|
+
readonly run: (request: (client: Client) => Promise<{
|
|
7
|
+
readonly data?: unknown;
|
|
8
|
+
readonly error?: unknown;
|
|
9
|
+
readonly response: Response;
|
|
10
|
+
}>, options?: {
|
|
11
|
+
readonly retryTransportOnce?: boolean;
|
|
12
|
+
readonly ambiguousOutcomeMessage?: string;
|
|
13
|
+
}) => Promise<number>;
|
|
14
|
+
readonly setExitCode: (value: number) => void;
|
|
15
|
+
readonly canWrite: () => boolean;
|
|
16
|
+
readonly rejectWrite: () => number;
|
|
17
|
+
readonly parseOpaqueVersion: (value: string) => string;
|
|
18
|
+
readonly parseLimit: (value: string) => number;
|
|
19
|
+
}
|
|
20
|
+
/** Extend the existing resource-oriented CLI. Targets remain independent
|
|
21
|
+
* resources; these commands never fetch external content or select a Revision. */
|
|
22
|
+
export declare function registerWorkItemResourceLinkCommands(workItem: Command, spec: Command, runtime: Runtime): void;
|
|
23
|
+
export {};
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { InvalidArgumentError } from "commander";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
const uuid = (value) => {
|
|
4
|
+
if (!z.uuid().safeParse(value).success)
|
|
5
|
+
throw new InvalidArgumentError("Resource link and command identities must be UUIDs");
|
|
6
|
+
return value;
|
|
7
|
+
};
|
|
8
|
+
const kind = (value) => {
|
|
9
|
+
const parsed = z.enum(["spec", "document", "other"]).safeParse(value);
|
|
10
|
+
if (!parsed.success)
|
|
11
|
+
throw new InvalidArgumentError("Resource kind must be spec, document, or other");
|
|
12
|
+
return parsed.data;
|
|
13
|
+
};
|
|
14
|
+
function targetFor(options) {
|
|
15
|
+
if (options.kind === "spec") {
|
|
16
|
+
if (options.specId === undefined || options.title !== undefined || options.url !== undefined)
|
|
17
|
+
throw new InvalidArgumentError("Spec links require --spec-id and do not accept --title or --url");
|
|
18
|
+
return { kind: "spec", projectId: options.specProjectId ?? options.projectId, specId: options.specId };
|
|
19
|
+
}
|
|
20
|
+
if (options.title === undefined || options.url === undefined || options.specId !== undefined || options.specProjectId !== undefined)
|
|
21
|
+
throw new InvalidArgumentError("Document and other links require --title and --url and do not accept Spec flags");
|
|
22
|
+
return { kind: options.kind, title: options.title, url: options.url };
|
|
23
|
+
}
|
|
24
|
+
/** Extend the existing resource-oriented CLI. Targets remain independent
|
|
25
|
+
* resources; these commands never fetch external content or select a Revision. */
|
|
26
|
+
export function registerWorkItemResourceLinkCommands(workItem, spec, runtime) {
|
|
27
|
+
workItem.command("github-links").description("Read stored GitHub issue, pull request and branch associations")
|
|
28
|
+
.requiredOption("--project-id <projectId>")
|
|
29
|
+
.requiredOption("--work-item-id <workItemId>")
|
|
30
|
+
.action(async (options) => {
|
|
31
|
+
runtime.setExitCode(await runtime.run((client) => client.GET("/v1/projects/{projectId}/work-items/{workItemId}/github-links", { params: { path: options } })));
|
|
32
|
+
});
|
|
33
|
+
const links = workItem.command("resource-link").description("Manage optional Spec, document, and other task references");
|
|
34
|
+
links.command("list")
|
|
35
|
+
.requiredOption("--project-id <projectId>")
|
|
36
|
+
.requiredOption("--work-item-id <workItemId>")
|
|
37
|
+
.action(async (options) => {
|
|
38
|
+
runtime.setExitCode(await runtime.run((client) => client.GET("/v1/projects/{projectId}/work-items/{workItemId}/resource-links", { params: { path: options } })));
|
|
39
|
+
});
|
|
40
|
+
links.command("add")
|
|
41
|
+
.requiredOption("--project-id <projectId>")
|
|
42
|
+
.requiredOption("--work-item-id <workItemId>")
|
|
43
|
+
.requiredOption("--kind <kind>", "spec, document, or other", kind)
|
|
44
|
+
.option("--spec-project-id <projectId>", "Target Spec Project; defaults to the task Project")
|
|
45
|
+
.option("--spec-id <specId>", "Stable public Spec identity")
|
|
46
|
+
.option("--title <title>", "Document or other resource label")
|
|
47
|
+
.option("--url <url>", "HTTP(S) resource locator, without embedded credentials")
|
|
48
|
+
.requiredOption("--idempotency-key <key>", "Reuse the same key and target when retrying", uuid)
|
|
49
|
+
.action(async (options) => {
|
|
50
|
+
if (!runtime.canWrite()) {
|
|
51
|
+
runtime.setExitCode(runtime.rejectWrite());
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
const target = targetFor(options);
|
|
55
|
+
runtime.setExitCode(await runtime.run((client) => client.POST("/v1/projects/{projectId}/work-items/{workItemId}/resource-links", {
|
|
56
|
+
body: { target }, params: { path: { projectId: options.projectId, workItemId: options.workItemId }, header: { "idempotency-key": options.idempotencyKey } },
|
|
57
|
+
}), { retryTransportOnce: true, ambiguousOutcomeMessage: "Resource link outcome is unknown; retry the same target and command key" }));
|
|
58
|
+
});
|
|
59
|
+
links.command("remove")
|
|
60
|
+
.requiredOption("--project-id <projectId>")
|
|
61
|
+
.requiredOption("--work-item-id <workItemId>")
|
|
62
|
+
.requiredOption("--link-id <linkId>", "Resource link UUID returned by list", uuid)
|
|
63
|
+
.requiredOption("--expected-version <version>", "Opaque link version returned by list", runtime.parseOpaqueVersion)
|
|
64
|
+
.requiredOption("--idempotency-key <key>", "Reuse the same key and version when retrying", uuid)
|
|
65
|
+
.action(async (options) => {
|
|
66
|
+
if (!runtime.canWrite()) {
|
|
67
|
+
runtime.setExitCode(runtime.rejectWrite());
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
runtime.setExitCode(await runtime.run((client) => client.DELETE("/v1/projects/{projectId}/work-items/{workItemId}/resource-links/{linkId}", {
|
|
71
|
+
params: { path: { projectId: options.projectId, workItemId: options.workItemId, linkId: options.linkId }, header: { "if-match": options.expectedVersion, "idempotency-key": options.idempotencyKey } },
|
|
72
|
+
}), { retryTransportOnce: true, ambiguousOutcomeMessage: "Resource unlink outcome is unknown; retry the same link, version and command key" }));
|
|
73
|
+
});
|
|
74
|
+
spec.command("work-items").description("Read currently authorized tasks referencing a Spec")
|
|
75
|
+
.requiredOption("--project-id <projectId>")
|
|
76
|
+
.requiredOption("--spec-id <specId>")
|
|
77
|
+
.option("--limit <limit>", "Maximum results, 1 through 100", runtime.parseLimit, 50)
|
|
78
|
+
.option("--cursor <cursor>", "Opaque cursor from the previous page")
|
|
79
|
+
.action(async (options) => {
|
|
80
|
+
runtime.setExitCode(await runtime.run((client) => client.GET("/v1/projects/{projectId}/specs/{specId}/work-items", {
|
|
81
|
+
params: { path: { projectId: options.projectId, specId: options.specId }, query: { limit: options.limit, ...(options.cursor === undefined ? {} : { cursor: options.cursor }) } },
|
|
82
|
+
})));
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=work-item-resource-link-commands.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"work-item-resource-link-commands.js","sourceRoot":"","sources":["../src/work-item-resource-link-commands.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** ISO/calendar validation ported from Kaneo time-entry/schema.ts (MIT). */
|
|
2
|
+
export declare function parseTimeEntryTimestamp(value: string): string;
|
|
3
|
+
export declare function parseTimeEntryUuid(value: string): string;
|
|
4
|
+
export declare function parseTimeEntryDescription(value: string): string;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/** ISO/calendar validation ported from Kaneo time-entry/schema.ts (MIT). */
|
|
3
|
+
export function parseTimeEntryTimestamp(value) {
|
|
4
|
+
const validFormat = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(:\d{2}(\.\d{1,9})?)?(Z|[+-]\d{2}:\d{2})$/u.test(value);
|
|
5
|
+
const year = Number(value.slice(0, 4));
|
|
6
|
+
const month = Number(value.slice(5, 7));
|
|
7
|
+
const day = Number(value.slice(8, 10));
|
|
8
|
+
const calendar = new Date(0);
|
|
9
|
+
calendar.setUTCFullYear(year, month - 1, day);
|
|
10
|
+
if (!validFormat || Number.isNaN(Date.parse(value)) || year === 0 || calendar.getUTCFullYear() !== year || calendar.getUTCMonth() !== month - 1 || calendar.getUTCDate() !== day)
|
|
11
|
+
throw new Error("Time Entry timestamp must be a valid ISO 8601 date-time with an offset");
|
|
12
|
+
const timestamp = new Date(value);
|
|
13
|
+
if (timestamp.getUTCFullYear() < 1 || timestamp.getUTCFullYear() > 9999)
|
|
14
|
+
throw new Error("Time Entry UTC year must be between 1 and 9999");
|
|
15
|
+
return timestamp.toISOString();
|
|
16
|
+
}
|
|
17
|
+
export function parseTimeEntryUuid(value) {
|
|
18
|
+
if (!z.uuid().safeParse(value).success)
|
|
19
|
+
throw new Error("Time Entry identifier and command key must be UUIDs");
|
|
20
|
+
return value;
|
|
21
|
+
}
|
|
22
|
+
export function parseTimeEntryDescription(value) {
|
|
23
|
+
if (value.length > 20_000 || value.includes("\0"))
|
|
24
|
+
throw new Error("Time Entry description exceeds the allowed limit");
|
|
25
|
+
return value;
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=work-item-time-entry-input.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"work-item-time-entry-input.js","sourceRoot":"","sources":["../src/work-item-time-entry-input.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,4EAA4E;AAC5E,MAAM,UAAU,uBAAuB,CAAC,KAAa;IACnD,MAAM,WAAW,GAAG,0EAA0E,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC3G,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IACvC,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IACxC,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;IACvC,MAAM,QAAQ,GAAG,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC;IAC7B,QAAQ,CAAC,cAAc,CAAC,IAAI,EAAE,KAAK,GAAG,CAAC,EAAE,GAAG,CAAC,CAAC;IAC9C,IAAI,CAAC,WAAW,IAAI,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,IAAI,IAAI,KAAK,CAAC,IAAI,QAAQ,CAAC,cAAc,EAAE,KAAK,IAAI,IAAI,QAAQ,CAAC,WAAW,EAAE,KAAK,KAAK,GAAG,CAAC,IAAI,QAAQ,CAAC,UAAU,EAAE,KAAK,GAAG;QAAE,MAAM,IAAI,KAAK,CAAC,wEAAwE,CAAC,CAAC;IAC5Q,MAAM,SAAS,GAAG,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC;IAClC,IAAI,SAAS,CAAC,cAAc,EAAE,GAAG,CAAC,IAAI,SAAS,CAAC,cAAc,EAAE,GAAG,IAAI;QAAE,MAAM,IAAI,KAAK,CAAC,gDAAgD,CAAC,CAAC;IAC3I,OAAO,SAAS,CAAC,WAAW,EAAE,CAAC;AACjC,CAAC;AACD,MAAM,UAAU,kBAAkB,CAAC,KAAa;IAC9C,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,OAAO;QAAE,MAAM,IAAI,KAAK,CAAC,qDAAqD,CAAC,CAAC;IAC/G,OAAO,KAAK,CAAC;AACf,CAAC;AACD,MAAM,UAAU,yBAAyB,CAAC,KAAa;IACrD,IAAI,KAAK,CAAC,MAAM,GAAG,MAAM,IAAI,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,kDAAkD,CAAC,CAAC;IACvH,OAAO,KAAK,CAAC;AACf,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,56 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gonovus-ai/spec-flow-cli",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "SpecFlow supported command-line client",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "git+https://github.com/gonovus-ai/spec-flow.git",
|
|
8
|
+
"directory": "packages/cli"
|
|
9
|
+
},
|
|
10
|
+
"publishConfig": {
|
|
11
|
+
"access": "public",
|
|
12
|
+
"registry": "https://registry.npmjs.org"
|
|
13
|
+
},
|
|
14
|
+
"type": "module",
|
|
15
|
+
"bin": {
|
|
16
|
+
"specflow": "./dist/bin.js"
|
|
17
|
+
},
|
|
18
|
+
"exports": {
|
|
19
|
+
".": {
|
|
20
|
+
"types": "./dist/index.d.ts",
|
|
21
|
+
"import": "./dist/index.js"
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"files": [
|
|
25
|
+
"dist"
|
|
26
|
+
],
|
|
27
|
+
"engines": {
|
|
28
|
+
"node": ">=24.0.0 <25.0.0"
|
|
29
|
+
},
|
|
30
|
+
"os": [
|
|
31
|
+
"darwin",
|
|
32
|
+
"linux",
|
|
33
|
+
"win32"
|
|
34
|
+
],
|
|
35
|
+
"cpu": [
|
|
36
|
+
"arm64",
|
|
37
|
+
"x64"
|
|
38
|
+
],
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"@napi-rs/keyring": "1.3.0",
|
|
41
|
+
"commander": "15.0.0",
|
|
42
|
+
"open": "11.0.0",
|
|
43
|
+
"openapi-fetch": "^0.17.0",
|
|
44
|
+
"zod": "4.4.3"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"@types/node": "24.13.3",
|
|
48
|
+
"@spec-flow/api-client": "0.0.0"
|
|
49
|
+
},
|
|
50
|
+
"scripts": {
|
|
51
|
+
"build": "tsc -p tsconfig.build.json && node ../../scripts/build-cli-default-origin.mjs",
|
|
52
|
+
"lint": "eslint .",
|
|
53
|
+
"test": "vitest run --root ../.. --project cli",
|
|
54
|
+
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
55
|
+
}
|
|
6
56
|
}
|