@weavix/cli 0.1.0-dev → 0.2.0-dev

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/build/index.js CHANGED
@@ -40,6 +40,24 @@ var CLI_NAME, CLI_DESCRIPTION, CLI_HELP_TEXT, init_meta = __esm({
40
40
  }
41
41
  });
42
42
 
43
+ // src/utils/verbose.ts
44
+ function setVerboseEnabled(value) {
45
+ verboseEnabled = value;
46
+ }
47
+ function isVerboseEnabled() {
48
+ return verboseEnabled;
49
+ }
50
+ function writeVerboseArtifact(relativePath, content) {
51
+ let targetPath = import_path.default.resolve(".tracker-cli-debug", relativePath);
52
+ return import_fs.default.mkdirSync(import_path.default.dirname(targetPath), { recursive: !0 }), import_fs.default.writeFileSync(targetPath, content), targetPath;
53
+ }
54
+ var import_fs, import_path, verboseEnabled, init_verbose = __esm({
55
+ "src/utils/verbose.ts"() {
56
+ "use strict";
57
+ import_fs = __toESM(require("fs")), import_path = __toESM(require("path")), verboseEnabled = !1;
58
+ }
59
+ });
60
+
43
61
  // src/utils/logger.ts
44
62
  function success(message) {
45
63
  console.info(import_picocolors.default.green(`\u2705 ${message}`)), newLine();
@@ -54,6 +72,9 @@ function warning(message) {
54
72
  function info(message) {
55
73
  console.info(message), newLine();
56
74
  }
75
+ function verbose(message) {
76
+ isVerboseEnabled() && (console.info(import_picocolors.default.dim(message)), newLine());
77
+ }
57
78
  function suggest(command) {
58
79
  console.info(`Run: ${command}`), newLine();
59
80
  }
@@ -73,11 +94,13 @@ var import_picocolors, logger, logger_default, init_logger = __esm({
73
94
  "src/utils/logger.ts"() {
74
95
  "use strict";
75
96
  import_picocolors = __toESM(require("picocolors"));
97
+ init_verbose();
76
98
  logger = {
77
99
  success,
78
100
  error,
79
101
  warning,
80
102
  info,
103
+ verbose,
81
104
  suggest,
82
105
  newLine,
83
106
  separator,
@@ -86,102 +109,112 @@ var import_picocolors, logger, logger_default, init_logger = __esm({
86
109
  }
87
110
  });
88
111
 
89
- // src/distributions/external/configStore.ts
90
- async function readConfig() {
91
- if (!(0, import_fs.existsSync)(CONFIG_FILE))
92
- return null;
93
- let raw = await (0, import_promises.readFile)(CONFIG_FILE, "utf-8");
94
- return JSON.parse(raw);
112
+ // src/distributions/external/config/configManager.ts
113
+ function getConfigPath() {
114
+ return CONFIG_FILE;
115
+ }
116
+ function readConfig() {
117
+ if (cached !== void 0)
118
+ return cached;
119
+ if (!(0, import_fs2.existsSync)(CONFIG_FILE))
120
+ return cached = null, cached;
121
+ try {
122
+ cached = JSON.parse((0, import_fs2.readFileSync)(CONFIG_FILE, "utf-8"));
123
+ } catch {
124
+ cached = null;
125
+ }
126
+ return cached;
127
+ }
128
+ function writeConfig(config) {
129
+ (0, import_fs2.existsSync)(CONFIG_DIR) || (0, import_fs2.mkdirSync)(CONFIG_DIR, { recursive: !0, mode: 448 }), (0, import_fs2.writeFileSync)(CONFIG_FILE, JSON.stringify(config, null, 2), { mode: 384 }), cached = config;
95
130
  }
96
- async function writeConfig(config) {
97
- (0, import_fs.existsSync)(CONFIG_DIR) || (0, import_fs.mkdirSync)(CONFIG_DIR, { recursive: !0, mode: 448 }), await (0, import_promises.writeFile)(CONFIG_FILE, JSON.stringify(config, null, 2), { mode: 384 });
131
+ function clearConfig() {
132
+ (0, import_fs2.existsSync)(CONFIG_FILE) && (0, import_fs2.rmSync)(CONFIG_FILE), cached = null;
98
133
  }
99
- async function clearConfig() {
100
- (0, import_fs.existsSync)(CONFIG_FILE) && await (0, import_promises.unlink)(CONFIG_FILE);
134
+ function requireConfig() {
135
+ let config = readConfig();
136
+ if (!config)
137
+ throw new Error('CLI is not configured. Run "weavix config set" to configure.');
138
+ return config;
101
139
  }
102
- var import_fs, import_promises, os, import_path, CONFIG_DIR, CONFIG_FILE, CONFIG_FILE_PATH, init_configStore = __esm({
103
- "src/distributions/external/configStore.ts"() {
140
+ var import_fs2, os, import_path2, CONFIG_DIR, CONFIG_FILE, cached, init_configManager = __esm({
141
+ "src/distributions/external/config/configManager.ts"() {
104
142
  "use strict";
105
- import_fs = require("fs"), import_promises = require("fs/promises"), os = __toESM(require("os")), import_path = __toESM(require("path")), CONFIG_DIR = import_path.default.join(os.homedir(), ".tracker-cli"), CONFIG_FILE = import_path.default.join(CONFIG_DIR, "config.json"), CONFIG_FILE_PATH = CONFIG_FILE;
143
+ import_fs2 = require("fs"), os = __toESM(require("os")), import_path2 = __toESM(require("path")), CONFIG_DIR = import_path2.default.join(os.homedir(), ".tracker-cli"), CONFIG_FILE = import_path2.default.join(CONFIG_DIR, "config.json");
106
144
  }
107
145
  });
108
146
 
109
- // src/distributions/external/configCommand.ts
110
- function withErrorHandling(fn) {
111
- return async (...args) => {
112
- try {
113
- await fn(...args);
114
- } catch (error2) {
115
- error2 instanceof Error && error2.name === "ExitPromptError" && process.exit(0), logger_default.error(error2), process.exit(1);
116
- }
117
- };
118
- }
119
- async function configSet() {
120
- let current = await readConfig(), awsEndpoint = await (0, import_prompts.input)({
121
- message: "S3 endpoint (e.g. https://s3.amazonaws.com):",
122
- default: current?.awsEndpoint,
123
- validate: (v) => v.trim() ? !0 : "required"
124
- }), awsRegion = await (0, import_prompts.input)({
125
- message: "S3 region:",
126
- default: current?.awsRegion,
127
- validate: (v) => v.trim() ? !0 : "required"
128
- }), staticBucket = await (0, import_prompts.input)({
129
- message: "Bucket for plugin files:",
130
- default: current?.staticBucket,
131
- validate: (v) => v.trim() ? !0 : "required"
132
- }), configBucket = await (0, import_prompts.input)({
133
- message: "Bucket for plugins config.json:",
134
- default: current?.configBucket,
135
- validate: (v) => v.trim() ? !0 : "required"
136
- }), mdsEndpoint = await (0, import_prompts.input)({
137
- message: "MDS endpoint (optional, defaults to S3 endpoint):",
138
- default: current?.mdsEndpoint || ""
139
- }), platformApiUrl = await (0, import_prompts.input)({
140
- message: "Platform API URL (optional):",
141
- default: current?.platformApiUrl || ""
142
- }), trackerApiUrl = await (0, import_prompts.input)({
143
- message: "Tracker API URL (optional):",
144
- default: current?.trackerApiUrl || ""
145
- }), config = {
146
- awsEndpoint: awsEndpoint.trim(),
147
- awsRegion: awsRegion.trim(),
148
- staticBucket: staticBucket.trim(),
149
- configBucket: configBucket.trim()
150
- };
151
- mdsEndpoint.trim() && (config.mdsEndpoint = mdsEndpoint.trim()), platformApiUrl.trim() && (config.platformApiUrl = platformApiUrl.trim()), trackerApiUrl.trim() && (config.trackerApiUrl = trackerApiUrl.trim()), await writeConfig(config), logger_default.success(`Configuration saved to ${CONFIG_FILE_PATH}`);
147
+ // src/distributions/external/config/index.ts
148
+ async function promptString(message, current, required = !0) {
149
+ return (await (0, import_prompts.input)({
150
+ message: current ? `${message} (current: ${current})` : message,
151
+ default: current,
152
+ required: required && !current
153
+ }))?.trim() || void 0;
154
+ }
155
+ async function runConfigSet() {
156
+ let existing = readConfig() ?? {};
157
+ logger_default.info("Configure CLI endpoints. Empty input keeps the current value.");
158
+ let s3Endpoint = await promptString("S3 endpoint", existing.s3?.endpoint), s3Region = await promptString("S3 region", existing.s3?.region), mdsEndpoint = await promptString("MDS endpoint", existing.mds?.endpoint), testingStatic = await promptString(
159
+ "Testing bucket (static)",
160
+ existing.buckets?.testing?.static
161
+ ), testingConfig = await promptString(
162
+ "Testing bucket (config)",
163
+ existing.buckets?.testing?.config
164
+ ), productionStatic = await promptString(
165
+ "Production bucket (static)",
166
+ existing.buckets?.production?.static
167
+ ), productionConfig = await promptString(
168
+ "Production bucket (config)",
169
+ existing.buckets?.production?.config
170
+ ), pluginDownloadTesting = await promptString(
171
+ "Plugin download base URL (testing)",
172
+ existing.pluginDownloadBase?.testing
173
+ ), pluginDownloadProduction = await promptString(
174
+ "Plugin download base URL (production)",
175
+ existing.pluginDownloadBase?.production
176
+ ), platformBaseUrl = await promptString(
177
+ "Platform API base URL",
178
+ existing.api?.platformBaseUrl
179
+ ), trackerBaseUrl = await promptString("Tracker API base URL", existing.api?.trackerBaseUrl), oauthUrl = await promptString("OAuth token URL", existing.auth?.oauthUrl);
180
+ writeConfig({
181
+ s3: { endpoint: s3Endpoint, region: s3Region },
182
+ mds: { endpoint: mdsEndpoint },
183
+ buckets: {
184
+ testing: { static: testingStatic, config: testingConfig },
185
+ production: { static: productionStatic, config: productionConfig }
186
+ },
187
+ pluginDownloadBase: {
188
+ testing: pluginDownloadTesting,
189
+ production: pluginDownloadProduction
190
+ },
191
+ api: { platformBaseUrl, trackerBaseUrl },
192
+ auth: { oauthUrl }
193
+ }), logger_default.success(`Configuration saved \u2192 ${getConfigPath()}`);
152
194
  }
153
- async function configShow() {
154
- let cfg = await readConfig();
195
+ function runConfigShow() {
196
+ let cfg = readConfig();
155
197
  if (!cfg) {
156
- logger_default.info("No configuration. Run: tracker-cli config set");
198
+ logger_default.info(
199
+ `No configuration found at ${getConfigPath()}. Run "weavix config set" to create one.`
200
+ );
157
201
  return;
158
202
  }
159
- logger_default.info(JSON.stringify(cfg, null, 2));
203
+ logger_default.info(`Configuration at ${getConfigPath()}:`), logger_default.info(JSON.stringify(cfg, null, 2));
160
204
  }
161
- async function configClear() {
162
- if (!await readConfig()) {
163
- logger_default.info("No configuration to clear.");
164
- return;
165
- }
166
- if (!await (0, import_prompts.confirm)({
167
- message: `Remove configuration at ${CONFIG_FILE_PATH}?`,
168
- default: !1
169
- })) {
170
- logger_default.info("Cancelled.");
171
- return;
172
- }
173
- await clearConfig(), logger_default.success("Configuration removed.");
205
+ function runConfigClear() {
206
+ clearConfig(), logger_default.success(`Configuration removed (${getConfigPath()})`);
174
207
  }
175
208
  function registerConfigCommand(program) {
176
- let config = program.command("config").description("manage CLI configuration (S3 endpoints, buckets, API URLs)");
177
- config.command("set").description("set configuration interactively").action(withErrorHandling(configSet)), config.command("show").description("show current configuration").action(withErrorHandling(configShow)), config.command("clear").description("remove configuration").action(withErrorHandling(configClear));
209
+ let config = program.command("config").description("manage CLI configuration");
210
+ config.command("set").description("set CLI configuration values interactively").action(runConfigSet), config.command("show").description("display current CLI configuration").action(runConfigShow), config.command("clear").description("remove stored CLI configuration").action(runConfigClear);
178
211
  }
179
- var import_prompts, init_configCommand = __esm({
180
- "src/distributions/external/configCommand.ts"() {
212
+ var import_prompts, init_config = __esm({
213
+ "src/distributions/external/config/index.ts"() {
181
214
  "use strict";
182
215
  import_prompts = require("@inquirer/prompts");
183
216
  init_logger();
184
- init_configStore();
217
+ init_configManager();
185
218
  }
186
219
  });
187
220
 
@@ -196,92 +229,85 @@ function registerDistributionCommands(program) {
196
229
  var init_registerCommands = __esm({
197
230
  "src/distributions/external/registerCommands.ts"() {
198
231
  "use strict";
199
- init_configCommand();
232
+ init_config();
200
233
  }
201
234
  });
202
235
 
203
- // src/distributions/external/auth.ts
204
- var auth_exports = {};
205
- __export(auth_exports, {
206
- getNpmUpdateUrl: () => getNpmUpdateUrl,
207
- getOAuthTokenUrl: () => getOAuthTokenUrl
236
+ // src/distributions/external/hosts.ts
237
+ var hosts_exports = {};
238
+ __export(hosts_exports, {
239
+ getBuckets: () => getBuckets,
240
+ getMdsEndpoint: () => getMdsEndpoint,
241
+ getOAuthTokenUrl: () => getOAuthTokenUrl,
242
+ getPlatformBaseUrl: () => getPlatformBaseUrl,
243
+ getPluginDownloadBase: () => getPluginDownloadBase,
244
+ getS3Endpoint: () => getS3Endpoint,
245
+ getS3Region: () => getS3Region,
246
+ getTrackerBaseUrl: () => getTrackerBaseUrl,
247
+ getUpdateCheckInfo: () => getUpdateCheckInfo
208
248
  });
209
- async function getOAuthTokenUrl() {
249
+ function getS3Endpoint() {
250
+ let cfg = requireConfig();
251
+ if (!cfg.s3?.endpoint)
252
+ throw new Error('S3 endpoint is not configured. Run "weavix config set" to configure.');
253
+ return cfg.s3.endpoint;
254
+ }
255
+ function getMdsEndpoint() {
256
+ let cfg = requireConfig();
257
+ if (!cfg.mds?.endpoint)
258
+ throw new Error('MDS endpoint is not configured. Run "weavix config set" to configure.');
259
+ return cfg.mds.endpoint;
260
+ }
261
+ function getS3Region() {
262
+ let cfg = requireConfig();
263
+ if (!cfg.s3?.region)
264
+ throw new Error('S3 region is not configured. Run "weavix config set" to configure.');
265
+ return cfg.s3.region;
266
+ }
267
+ function getBuckets() {
268
+ let buckets = requireConfig().buckets, testing = buckets?.testing, production = buckets?.production;
269
+ if (!testing?.static || !testing?.config || !production?.static || !production?.config)
270
+ throw new Error('S3 buckets are not configured. Run "weavix config set" to configure.');
271
+ return {
272
+ testing: { static: testing.static, config: testing.config },
273
+ production: { static: production.static, config: production.config }
274
+ };
210
275
  }
211
- async function getNpmUpdateUrl() {
276
+ function getPluginDownloadBase(env) {
277
+ let base = requireConfig().pluginDownloadBase?.[env];
278
+ if (!base)
279
+ throw new Error(
280
+ `Plugin download base for "${env}" is not configured. Run "weavix config set" to configure.`
281
+ );
282
+ return base;
212
283
  }
213
- var init_auth = __esm({
214
- "src/distributions/external/auth.ts"() {
215
- "use strict";
216
- }
217
- });
218
-
219
- // src/distributions/external/apiUrls.ts
220
- var apiUrls_exports = {};
221
- __export(apiUrls_exports, {
222
- getPlatformApiUrl: () => getPlatformApiUrl,
223
- getTrackerApiUrl: () => getTrackerApiUrl
224
- });
225
- async function getPlatformApiUrl() {
226
- let cfg = await readConfig();
227
- if (!cfg?.platformApiUrl)
228
- throw new Error("Platform API URL is not configured. Run: tracker-cli config set");
229
- return cfg.platformApiUrl;
230
- }
231
- async function getTrackerApiUrl() {
232
- let cfg = await readConfig();
233
- if (!cfg?.trackerApiUrl)
234
- throw new Error("Tracker API URL is not configured. Run: tracker-cli config set");
235
- return cfg.trackerApiUrl;
236
- }
237
- var init_apiUrls = __esm({
238
- "src/distributions/external/apiUrls.ts"() {
239
- "use strict";
240
- init_configStore();
241
- }
242
- });
243
-
244
- // src/distributions/external/promptEnvironment.ts
245
- var promptEnvironment_exports = {};
246
- __export(promptEnvironment_exports, {
247
- promptEnvironment: () => promptEnvironment
248
- });
249
- async function promptEnvironment() {
250
- return "";
284
+ function getPlatformBaseUrl() {
285
+ let cfg = requireConfig();
286
+ if (!cfg.api?.platformBaseUrl)
287
+ throw new Error(
288
+ 'Platform API URL is not configured. Run "weavix config set" to configure.'
289
+ );
290
+ return cfg.api.platformBaseUrl;
251
291
  }
252
- var init_promptEnvironment = __esm({
253
- "src/distributions/external/promptEnvironment.ts"() {
254
- "use strict";
255
- }
256
- });
257
-
258
- // src/distributions/external/storage.ts
259
- var storage_exports = {};
260
- __export(storage_exports, {
261
- DEFAULT_ENVIRONMENT: () => DEFAULT_ENVIRONMENT,
262
- getStorageSettings: () => getStorageSettings,
263
- listEnvironments: () => listEnvironments
264
- });
265
- async function getStorageSettings(_environment) {
266
- let cfg = await readConfig();
267
- if (!cfg)
268
- throw new Error("CLI is not configured. Run: tracker-cli config set");
269
- return {
270
- awsEndpoint: cfg.awsEndpoint,
271
- awsRegion: cfg.awsRegion,
272
- mdsEndpoint: cfg.mdsEndpoint ?? cfg.awsEndpoint,
273
- configBucket: cfg.configBucket,
274
- staticBucket: cfg.staticBucket
275
- };
292
+ function getTrackerBaseUrl() {
293
+ let cfg = requireConfig();
294
+ if (!cfg.api?.trackerBaseUrl)
295
+ throw new Error('Tracker API URL is not configured. Run "weavix config set" to configure.');
296
+ return cfg.api.trackerBaseUrl;
297
+ }
298
+ function getOAuthTokenUrl() {
299
+ let cfg = requireConfig();
300
+ if (!cfg.auth?.oauthUrl)
301
+ throw new Error('OAuth URL is not configured. Run "weavix config set" to configure.');
302
+ return cfg.auth.oauthUrl;
276
303
  }
277
- async function listEnvironments() {
278
- return [];
304
+ function getUpdateCheckInfo() {
305
+ return null;
279
306
  }
280
- var DEFAULT_ENVIRONMENT, init_storage = __esm({
281
- "src/distributions/external/storage.ts"() {
307
+ var init_hosts = __esm({
308
+ "src/distributions/external/hosts.ts"() {
282
309
  "use strict";
283
- init_configStore();
284
- DEFAULT_ENVIRONMENT = "";
310
+ init_configManager();
285
311
  }
286
312
  });
287
313
 
@@ -297,26 +323,35 @@ var impl2 = (init_registerCommands(), __toCommonJS(registerCommands_exports)), {
297
323
  // src/utils/checkUpdate.ts
298
324
  var import_node_fetch = __toESM(require("node-fetch"));
299
325
 
300
- // src/distribution/auth.ts
301
- var impl3 = (init_auth(), __toCommonJS(auth_exports)), { getOAuthTokenUrl: getOAuthTokenUrl2, getNpmUpdateUrl: getNpmUpdateUrl2 } = impl3;
326
+ // src/distribution/hosts.ts
327
+ var impl3 = (init_hosts(), __toCommonJS(hosts_exports)), {
328
+ getS3Endpoint: getS3Endpoint2,
329
+ getMdsEndpoint: getMdsEndpoint2,
330
+ getS3Region: getS3Region2,
331
+ getBuckets: getBuckets2,
332
+ getPluginDownloadBase: getPluginDownloadBase2,
333
+ getPlatformBaseUrl: getPlatformBaseUrl2,
334
+ getTrackerBaseUrl: getTrackerBaseUrl2,
335
+ getOAuthTokenUrl: getOAuthTokenUrl2,
336
+ getUpdateCheckInfo: getUpdateCheckInfo2
337
+ } = impl3;
302
338
 
303
339
  // src/utils/checkUpdate.ts
304
340
  init_logger();
305
341
  var checkUpdate = async (version) => {
306
- try {
307
- let registryUrl = await getNpmUpdateUrl2();
308
- if (!registryUrl)
309
- return;
310
- let response = await (0, import_node_fetch.default)(`${registryUrl}/@yandex-data-ui/tracker-cli/latest`);
311
- if (!response.ok)
312
- return;
313
- let latest = await response.json();
314
- latest.version !== version && (logger_default.newLine(), logger_default.info(
315
- `\u{1F514} Update available ${version} \u2192 ${latest.version}
316
- Run: npm i -g @yandex-data-ui/tracker-cli@latest --registry=${registryUrl}`
317
- ));
318
- } catch {
319
- }
342
+ let info3 = getUpdateCheckInfo2();
343
+ if (info3)
344
+ try {
345
+ let response = await (0, import_node_fetch.default)(`${info3.registryUrl}/${info3.packageName}/latest`);
346
+ if (!response.ok)
347
+ return;
348
+ let latest = await response.json();
349
+ latest.version !== version && (logger_default.newLine(), logger_default.info(
350
+ `\u{1F514} Update available ${version} \u2192 ${latest.version}
351
+ Run: npm i -g ${info3.packageName}@latest --registry=${info3.registryUrl}`
352
+ ));
353
+ } catch {
354
+ }
320
355
  };
321
356
 
322
357
  // src/utils/execCommand.ts
@@ -346,173 +381,269 @@ var build = async ({ environment = "production" } = {}) => {
346
381
  );
347
382
  };
348
383
 
349
- // src/api/tracker.ts
350
- var import_form_data = __toESM(require("form-data"));
384
+ // src/commands/create/index.ts
385
+ var path8 = __toESM(require("path")), import_prompts2 = require("@inquirer/prompts");
351
386
 
352
- // src/api/trackerApiRequest.ts
353
- var import_node_fetch2 = __toESM(require("node-fetch"));
387
+ // src/core/manifest/constants.ts
388
+ var DATA_PERMISSIONS = [
389
+ // top 10
390
+ "tracker:issues:read",
391
+ "tracker:issues:write",
392
+ "tracker:issuetypes:read",
393
+ "tracker:statuses:read",
394
+ "tracker:queues:read",
395
+ "tracker:fields:read",
396
+ "tracker:entities:read",
397
+ "tracker:attachments:read",
398
+ "tracker:attachments:write",
399
+ // in alphabetical order
400
+ "tracker:applications:read",
401
+ "tracker:boards:read",
402
+ "tracker:boards:write",
403
+ "tracker:bulk:read",
404
+ "tracker:bulk:write",
405
+ "tracker:bulkchange:read",
406
+ "tracker:bulkchange:write",
407
+ "tracker:charts:read",
408
+ "tracker:commentTemplates:read",
409
+ "tracker:commentTemplates:write",
410
+ "tracker:components:read",
411
+ "tracker:components:write",
412
+ "tracker:data:read",
413
+ "tracker:departments:read",
414
+ "tracker:entities:write",
415
+ "tracker:externalEventTypes:write",
416
+ "tracker:externalEvents:write",
417
+ "tracker:fields:write",
418
+ "tracker:filterFolders:write",
419
+ "tracker:filters:read",
420
+ "tracker:filters:write",
421
+ "tracker:goals:read",
422
+ "tracker:goals:write",
423
+ "tracker:groups:read",
424
+ "tracker:issueTemplates:read",
425
+ "tracker:issueTemplates:write",
426
+ "tracker:links:read",
427
+ "tracker:linktypes:read",
428
+ "tracker:liveBoards:write",
429
+ "tracker:localFields:read",
430
+ "tracker:maillists:read",
431
+ "tracker:maillists:write",
432
+ "tracker:myself:read",
433
+ "tracker:myself:write",
434
+ "tracker:priorities:read",
435
+ "tracker:queues:write",
436
+ "tracker:reminders:read",
437
+ "tracker:reminders:write",
438
+ "tracker:remotelinks:read",
439
+ "tracker:reports:read",
440
+ "tracker:reports:write",
441
+ "tracker:resolutions:read",
442
+ "tracker:resources:read",
443
+ "tracker:resources:write",
444
+ "tracker:roles:read",
445
+ "tracker:screens:read",
446
+ "tracker:services:read",
447
+ "tracker:sprints:read",
448
+ "tracker:sprints:write",
449
+ "tracker:system:write",
450
+ "tracker:tags:read",
451
+ "tracker:translations:read",
452
+ "tracker:users:read",
453
+ "tracker:users:write",
454
+ "tracker:versions:read",
455
+ "tracker:versions:write",
456
+ "tracker:workflows:read",
457
+ "tracker:workflows:write",
458
+ "tracker:worklog:read",
459
+ "tracker:worklog:write"
460
+ ], TOP_DATA_PERMISSIONS = DATA_PERMISSIONS.slice(0, 10);
461
+ var TEMPLATES = [
462
+ "default",
463
+ "issue.action",
464
+ "issue.block",
465
+ "issue.tab",
466
+ "issue.comment.action",
467
+ "navigation",
468
+ "trigger.action",
469
+ "project.action",
470
+ "portfolio.action",
471
+ "goal.action",
472
+ "issue.floatingbottom.action",
473
+ "attachment.viewer.action"
474
+ ];
475
+ var TEMPLATE_SLOTS = {
476
+ default: [],
477
+ "issue.action": ["issue.action"],
478
+ "issue.block": ["issue.block"],
479
+ "issue.tab": ["issue.tab"],
480
+ "issue.comment.action": ["issue.comment.action"],
481
+ navigation: ["navigation"],
482
+ "trigger.action": ["trigger.create.action", "trigger.edit.action"],
483
+ "project.action": ["project.action"],
484
+ "portfolio.action": ["portfolio.action"],
485
+ "goal.action": ["goal.action"],
486
+ "issue.floatingbottom.action": ["issue.floatingbottom.action"],
487
+ "attachment.viewer.action": ["attachment.viewer.action"]
488
+ };
354
489
 
355
- // src/core/auth/authManager.ts
356
- var fs = __toESM(require("fs")), os2 = __toESM(require("os")), path2 = __toESM(require("path")), authDir = path2.join(os2.homedir(), ".tracker-cli"), authFile = path2.join(authDir, "auth.json"), getData = () => {
357
- try {
358
- if (!fs.existsSync(authFile))
359
- return null;
360
- let data = fs.readFileSync(authFile, "utf-8");
361
- return JSON.parse(data);
362
- } catch {
363
- return null;
364
- }
365
- }, getAuthHeader = () => {
366
- let authData = getData();
367
- return authData ? `OAuth ${authData.token}` : null;
368
- }, saveAuthData = (token) => {
369
- let authData = {
370
- token
490
+ // src/commands/create/index.ts
491
+ init_logger();
492
+
493
+ // src/commands/create/utils/getWelcomeMessage.ts
494
+ var import_picocolors2 = __toESM(require("picocolors")), getWelcomeMessage = (pwd) => `
495
+ ${import_picocolors2.default.bold(import_picocolors2.default.white("\u{1F680} Create new tracker plugin"))}
496
+
497
+ ${import_picocolors2.default.dim(`\u{1F4C1} Creating an app in your current directory: ${pwd}`)}
498
+
499
+ ${import_picocolors2.default.dim("Press")} ${import_picocolors2.default.yellow("Ctrl+C")} ${import_picocolors2.default.dim("to cancel.")}
500
+ `;
501
+
502
+ // src/commands/create/utils/initManifest.ts
503
+ var fs2 = __toESM(require("fs")), path3 = __toESM(require("path"));
504
+
505
+ // src/commands/create/utils/generateManifest.ts
506
+ function buildSlots(template, slotTitle) {
507
+ let slotNames = TEMPLATE_SLOTS[template];
508
+ if (slotNames.length === 0) return;
509
+ let slotEntry = {
510
+ entrypoint: "index.html",
511
+ title: { ru: slotTitle, en: slotTitle }
371
512
  };
372
- fs.existsSync(authDir) || fs.mkdirSync(authDir, { recursive: !0, mode: 448 }), fs.writeFileSync(authFile, JSON.stringify(authData, null, 2), {
373
- mode: 384
374
- });
375
- }, removeAuthData = () => {
376
- fs.existsSync(authFile) && fs.unlinkSync(authFile);
377
- }, authManager = {
378
- get authHeader() {
379
- return getAuthHeader();
513
+ return {
514
+ tracker: Object.fromEntries(slotNames.map((slotName) => [slotName, [slotEntry]]))
515
+ };
516
+ }
517
+ var generateManifest = (answers) => ({
518
+ $schema: "./manifest.schema.json",
519
+ manifest_version: 1,
520
+ supported_services: ["tracker"],
521
+ slug: answers.packageSlug,
522
+ version: answers.version,
523
+ name: {
524
+ ru: answers.pluginName,
525
+ en: answers.pluginName
380
526
  },
381
- get data() {
382
- return getData();
527
+ description: {
528
+ ru: answers.pluginDescription,
529
+ en: answers.pluginDescription
383
530
  },
384
- saveAuthData,
385
- removeAuthData
531
+ permissions: answers.permissions,
532
+ support: [{ type: "email", value: answers.supportEmail }],
533
+ slots: buildSlots(answers.template, answers.slotTitle)
534
+ });
535
+
536
+ // src/commands/create/utils/initManifest.ts
537
+ var initManifest = ({ answers, targetDir }) => {
538
+ let generatedManifest = generateManifest(answers), manifestPath2 = path3.join(targetDir, "manifest.json");
539
+ fs2.writeFileSync(manifestPath2, JSON.stringify(generatedManifest, null, 2), "utf-8");
386
540
  };
387
541
 
388
- // src/distribution/apiUrls.ts
389
- var impl4 = (init_apiUrls(), __toCommonJS(apiUrls_exports)), { getPlatformApiUrl: getPlatformApiUrl2, getTrackerApiUrl: getTrackerApiUrl2 } = impl4;
542
+ // src/commands/create/utils/initTemplate.ts
543
+ var fs5 = __toESM(require("fs")), path7 = __toESM(require("path"));
390
544
 
391
- // src/api/trackerApiRequest.ts
392
- function buildQueryString(queryParams) {
393
- if (!queryParams || Object.keys(queryParams).length === 0)
394
- return "";
395
- let params = new URLSearchParams();
396
- return Object.entries(queryParams).forEach(([key, value]) => {
397
- params.append(key, String(value));
398
- }), `?${params.toString()}`;
399
- }
400
- async function parseResponse(response) {
401
- return response.status === 204 || response.headers.get("content-length") === "0" ? void 0 : response.headers.get("content-type")?.includes("application/json") ? await response.json() : await response.text();
402
- }
403
- async function trackerApiRequest(endpoint, options = {}) {
404
- let { method = "GET", body, headers = {}, formData, queryParams } = options, authHeader = authManager.authHeader;
405
- if (!authHeader)
406
- throw new Error('Not authenticated. Run "tracker-cli login" first.');
407
- let requestHeaders = {
408
- Authorization: authHeader,
409
- Accept: "application/json",
410
- ...headers
411
- }, requestBody;
412
- if (formData) {
413
- let formHeaders = formData.getHeaders?.();
414
- Object.assign(requestHeaders, formHeaders), requestBody = formData;
415
- } else body && method !== "GET" && (requestHeaders["Content-Type"] = "application/json", requestBody = JSON.stringify(body));
416
- let queryString = buildQueryString(queryParams), url = `${await getTrackerApiUrl2()}${endpoint}${queryString}`, response = await (0, import_node_fetch2.default)(url, {
417
- method,
418
- headers: requestHeaders,
419
- body: requestBody
545
+ // src/utils/copyDirectory.ts
546
+ var fs3 = __toESM(require("fs")), import_path3 = __toESM(require("path")), import_tinyglobby = require("tinyglobby"), resolveTargetFileName = (file) => file.endsWith(".template") ? "." + file.replace(".template", "") : file, copyDirectory = async (sourceDir, targetDir) => {
547
+ let files = await (0, import_tinyglobby.glob)(["**/*"], {
548
+ cwd: sourceDir,
549
+ onlyFiles: !0,
550
+ dot: !0
420
551
  });
421
- if (!response.ok) {
422
- let errorText = await response.text(), errorMessage = `Tracker API error ${response.status}: ${errorText}`;
423
- try {
424
- let errorJson = JSON.parse(errorText);
425
- errorJson.message && (errorMessage = `Tracker API error ${response.status}: ${errorJson.message}`);
426
- } catch {
427
- }
428
- throw new Error(errorMessage);
429
- }
430
- return parseResponse(response);
431
- }
552
+ await Promise.all(
553
+ files.map(async (file) => {
554
+ let sourcePath = import_path3.default.join(sourceDir, file), targetFile = resolveTargetFileName(file), targetPath = import_path3.default.join(targetDir, targetFile);
555
+ await fs3.promises.mkdir(import_path3.default.dirname(targetPath), { recursive: !0 }), await fs3.promises.copyFile(sourcePath, targetPath);
556
+ })
557
+ );
558
+ };
432
559
 
433
- // src/api/tracker.ts
434
- var PLUGINMOD_QUEUE = "PLUGINMOD";
435
- function buildIssueSummary(slug) {
436
- return `Plugin: ${slug}`;
437
- }
438
- function buildIssueDescription(params) {
439
- let { pluginId, slug, version, manifestJson, catalogName, catalogDescription, catalogTags } = params, lines = [
440
- `**Plugin UUID:** ${pluginId}`,
441
- `**Slug:** ${slug}`,
442
- `**Version:** ${version}`
443
- ];
444
- return catalogName && lines.push(`**Name:** ${catalogName}`), catalogDescription && lines.push(`**Description:** ${catalogDescription}`), catalogTags && catalogTags.length > 0 && lines.push(`**Tags:** ${catalogTags.join(", ")}`), lines.push("", "**Manifest:**", "```json", manifestJson, "```"), lines.join(`
560
+ // src/utils/getProjectPath.ts
561
+ var import_path4 = __toESM(require("path")), isDevelopment = () => __dirname.includes("/src/"), getProjectPath = (relativePath) => {
562
+ let levelsUp = isDevelopment() ? "../.." : "..";
563
+ return import_path4.default.resolve(__dirname, levelsUp, relativePath);
564
+ }, getTemplatesBaseDir = () => getProjectPath("templates"), getTemplatePath = (filename) => import_path4.default.join(getTemplatesBaseDir(), "_shared", filename);
565
+
566
+ // src/commands/create/utils/initTemplate.ts
567
+ init_logger();
568
+
569
+ // src/commands/create/utils/updatePackageJson.ts
570
+ var fs4 = __toESM(require("fs")), path6 = __toESM(require("path")), updatePackageJson = (targetDir, packageSlug) => {
571
+ let packageJsonPath = path6.join(targetDir, "package.json");
572
+ if (fs4.existsSync(packageJsonPath)) {
573
+ let packageJson = JSON.parse(fs4.readFileSync(packageJsonPath, "utf-8"));
574
+ packageJson.name = packageSlug, fs4.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 4) + `
445
575
  `);
576
+ }
577
+ };
578
+
579
+ // src/commands/create/utils/initTemplate.ts
580
+ var initTemplate = async ({ answers, targetDir }) => {
581
+ fs5.existsSync(targetDir) || fs5.mkdirSync(targetDir, { recursive: !0 });
582
+ let templatesBaseDir = getTemplatesBaseDir(), templatesDir = path7.join(templatesBaseDir, answers.template), sharedDir = path7.join(templatesBaseDir, "_shared");
583
+ await copyDirectory(sharedDir, targetDir), await copyDirectory(templatesDir, targetDir), updatePackageJson(targetDir, answers.packageSlug), logger_default.info("Created template with marketplace publication requirements."), logger_default.info(
584
+ "Before publishing, add ./marketplace/index.md, ./marketplace/header-image.jpg (784:325), and ./public/logo.svg (square)."
585
+ );
586
+ };
587
+
588
+ // src/commands/create/index.ts
589
+ var DEFAULT_VERSION = "0.0.1";
590
+ async function create() {
591
+ logger_default.info(getWelcomeMessage(process.cwd()));
592
+ let packageSlug = await (0, import_prompts2.input)({
593
+ message: "Enter app slug (e.g., my-tracker-plugin):",
594
+ required: !0
595
+ }), pluginName = await (0, import_prompts2.input)({
596
+ message: "Enter plugin name:",
597
+ required: !0
598
+ }), pluginDescription = await (0, import_prompts2.input)({
599
+ message: "Enter plugin description:",
600
+ required: !0
601
+ }), supportEmail = await (0, import_prompts2.input)({
602
+ message: "Enter support email:",
603
+ required: !0,
604
+ validate: (value) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value) || "Please enter a valid email address"
605
+ }), template = await (0, import_prompts2.select)({
606
+ message: "Select a template:",
607
+ choices: TEMPLATES.map((templateName) => ({
608
+ name: templateName,
609
+ value: templateName
610
+ }))
611
+ }), dataPermissions = await (0, import_prompts2.checkbox)({
612
+ message: "Select data permissions (some popular options):",
613
+ choices: TOP_DATA_PERMISSIONS.map((permission) => ({
614
+ name: permission,
615
+ value: permission
616
+ }))
617
+ }), answers = {
618
+ packageSlug,
619
+ slotTitle: packageSlug,
620
+ pluginName,
621
+ pluginDescription,
622
+ supportEmail,
623
+ template,
624
+ permissions: {
625
+ data: dataPermissions
626
+ },
627
+ version: DEFAULT_VERSION
628
+ }, targetDir = path8.join(process.cwd(), answers.packageSlug);
629
+ await initTemplate({
630
+ answers,
631
+ targetDir
632
+ }), initManifest({
633
+ answers,
634
+ targetDir
635
+ }), logger_default.newLine(), logger_default.success("Package generated!"), logger_default.commands("To get started, run", [
636
+ `cd ${answers.packageSlug}`,
637
+ "npm install",
638
+ "tracker-cli debug"
639
+ ]);
446
640
  }
447
- async function createPluginIssue(params) {
448
- let { slug, pluginId, version, manifestJson } = params, body = {
449
- queue: PLUGINMOD_QUEUE,
450
- summary: buildIssueSummary(slug),
451
- description: buildIssueDescription({ pluginId, slug, version, manifestJson }),
452
- tags: ["tracker-plugin"]
453
- };
454
- return trackerApiRequest("/issues", {
455
- method: "POST",
456
- body
457
- });
458
- }
459
- async function updatePluginIssue(issueKey, params) {
460
- return trackerApiRequest(`/issues/${issueKey}`, {
461
- method: "PATCH",
462
- body: params
463
- });
464
- }
465
- async function attachFileToIssue(issueKey, fileBuffer, fileName) {
466
- let formData = new import_form_data.default();
467
- return formData.append("file", fileBuffer, {
468
- filename: fileName,
469
- contentType: "application/zip"
470
- }), trackerApiRequest(`/issues/${issueKey}/attachments`, {
471
- method: "POST",
472
- formData
473
- });
474
- }
475
- async function getIssueAttachments(issueKey) {
476
- return trackerApiRequest(`/issues/${issueKey}/attachments`, {
477
- method: "GET"
478
- });
479
- }
480
- async function getIssueTransitions(issueKey) {
481
- return trackerApiRequest(`/issues/${issueKey}/transitions`, {
482
- method: "GET"
483
- });
484
- }
485
- async function executeIssueTransition(issueKey, transitionId) {
486
- await trackerApiRequest(`/issues/${issueKey}/transitions/${transitionId}/_execute`, {
487
- method: "POST",
488
- body: {}
489
- });
490
- }
491
- async function getIssue(issueKey) {
492
- return trackerApiRequest(`/issues/${issueKey}`, {
493
- method: "GET"
494
- });
495
- }
496
- async function getMyPluginIssues() {
497
- return trackerApiRequest("/issues/_search", {
498
- method: "POST",
499
- body: {
500
- filter: {
501
- queue: PLUGINMOD_QUEUE,
502
- createdBy: "me()"
503
- }
504
- }
505
- });
506
- }
507
- async function addIssueComment(issueKey, text) {
508
- await trackerApiRequest(`/issues/${issueKey}/comments`, {
509
- method: "POST",
510
- body: { text }
511
- });
512
- }
641
+
642
+ // src/commands/debug/index.ts
643
+ var import_fs7 = __toESM(require("fs")), import_path8 = __toESM(require("path"));
513
644
 
514
645
  // src/core/manifest/manifestManager.ts
515
- var import_fs2 = require("fs"), import_promises2 = require("fs/promises"), import_ajv = __toESM(require("ajv")), import_ajv_formats = __toESM(require("ajv-formats"));
646
+ var import_fs3 = require("fs"), import_promises = require("fs/promises"), import_ajv = __toESM(require("ajv")), import_ajv_formats = __toESM(require("ajv-formats"));
516
647
 
517
648
  // src/core/manifest/formatValidationErrors.ts
518
649
  var priorityOrder = [
@@ -539,14 +670,14 @@ var priorityOrder = [
539
670
  }, formatValidationErrors = (errors) => {
540
671
  let errorsByPath = {};
541
672
  for (let error2 of errors) {
542
- let path17 = error2.instancePath || "root";
543
- errorsByPath[path17] || (errorsByPath[path17] = []), errorsByPath[path17].push(error2);
673
+ let path18 = error2.instancePath || "root";
674
+ errorsByPath[path18] || (errorsByPath[path18] = []), errorsByPath[path18].push(error2);
544
675
  }
545
- return Object.entries(errorsByPath).map(([path17, pathErrors]) => {
676
+ return Object.entries(errorsByPath).map(([path18, pathErrors]) => {
546
677
  let error2 = [...pathErrors].sort(
547
678
  (a, b) => getPriority(a.keyword) - getPriority(b.keyword)
548
679
  )[0], formatter = errorMessages[error2.keyword], message = formatter ? formatter(error2.params) : error2.message;
549
- return ` - ${path17}: ${message}`;
680
+ return ` - ${path18}: ${message}`;
550
681
  }).join(`
551
682
  `);
552
683
  };
@@ -560,7 +691,10 @@ var manifest_schema_default = {
560
691
  required: [
561
692
  "slug",
562
693
  "version",
563
- "permissions"
694
+ "permissions",
695
+ "name",
696
+ "description",
697
+ "support"
564
698
  ],
565
699
  properties: {
566
700
  $schema: {
@@ -573,6 +707,23 @@ var manifest_schema_default = {
573
707
  maxLength: 63,
574
708
  pattern: "^[a-z0-9-]+$"
575
709
  },
710
+ manifest_version: {
711
+ type: "number",
712
+ description: "Manifest format version",
713
+ minimum: 1
714
+ },
715
+ supported_services: {
716
+ type: "array",
717
+ description: "Supported services for this plugin",
718
+ items: {
719
+ type: "string",
720
+ enum: [
721
+ "tracker"
722
+ ]
723
+ },
724
+ minItems: 1,
725
+ uniqueItems: !0
726
+ },
576
727
  slug: {
577
728
  type: "string",
578
729
  description: "Unique identifier for the plugin (lowercase letters, numbers, hyphen)",
@@ -621,13 +772,13 @@ var manifest_schema_default = {
621
772
  type: "string",
622
773
  description: "Russian description",
623
774
  minLength: 1,
624
- maxLength: 300
775
+ maxLength: 255
625
776
  },
626
777
  en: {
627
778
  type: "string",
628
779
  description: "English description",
629
780
  minLength: 1,
630
- maxLength: 300
781
+ maxLength: 255
631
782
  }
632
783
  },
633
784
  additionalProperties: !1
@@ -931,435 +1082,116 @@ var manifest_schema_default = {
931
1082
  description: "Configuration for a specific slot",
932
1083
  required: [
933
1084
  "entrypoint",
934
- "title"
935
- ],
936
- properties: {
937
- entrypoint: {
938
- type: "string",
939
- description: "Entry point file path for the slot",
940
- minLength: 1
941
- },
942
- title: {
943
- type: "object",
944
- description: "Localized titles for the slot",
945
- required: [
946
- "ru",
947
- "en"
948
- ],
949
- properties: {
950
- ru: {
951
- type: "string",
952
- description: "Russian title",
953
- minLength: 1,
954
- maxLength: 32
955
- },
956
- en: {
957
- type: "string",
958
- description: "English title",
959
- minLength: 1,
960
- maxLength: 32
961
- }
962
- },
963
- additionalProperties: !1
964
- },
965
- description: {
966
- type: "object",
967
- description: "Localized descriptions for the slot",
968
- required: [
969
- "ru",
970
- "en"
971
- ],
972
- properties: {
973
- ru: {
974
- type: "string",
975
- description: "Russian description",
976
- minLength: 1,
977
- maxLength: 300
978
- },
979
- en: {
980
- type: "string",
981
- description: "English description",
982
- minLength: 1,
983
- maxLength: 300
984
- }
985
- },
986
- additionalProperties: !1
987
- }
988
- },
989
- additionalProperties: !1
990
- }
991
- }
992
- };
993
-
994
- // src/core/manifest/manifestManager.ts
995
- var validator = (() => {
996
- let ajv = new import_ajv.default({ allErrors: !0, strict: !1 });
997
- return (0, import_ajv_formats.default)(ajv), ajv.compile(manifest_schema_default);
998
- })(), manifestPath = "./manifest.json", parseManifest = (content) => {
999
- let manifest;
1000
- try {
1001
- manifest = JSON.parse(content);
1002
- } catch (error2) {
1003
- throw error2 instanceof SyntaxError ? new Error(`File manifest.json contains invalid JSON: ${error2.message}`) : new Error(`Error reading manifest.json: ${error2.message}`);
1004
- }
1005
- return manifest;
1006
- }, loadRaw = async () => {
1007
- if (!(0, import_fs2.existsSync)(manifestPath))
1008
- throw new Error(`File manifest.json not found at path: ${manifestPath}`);
1009
- try {
1010
- return await (0, import_promises2.readFile)(manifestPath, "utf8");
1011
- } catch (error2) {
1012
- throw error2 instanceof Error ? new Error(`Error reading manifest.json: ${error2.message}`) : new Error("Error loading manifest");
1013
- }
1014
- }, load = async () => {
1015
- let manifestContent = await loadRaw();
1016
- return parseManifest(manifestContent);
1017
- }, validate = async () => {
1018
- let manifest = await load();
1019
- if (!validator(manifest) && validator.errors) {
1020
- let errorMessages2 = formatValidationErrors(validator.errors);
1021
- throw new Error(`Manifest validation failed:
1022
- ${errorMessages2}`);
1023
- }
1024
- }, save = async (manifest) => {
1025
- try {
1026
- let content = JSON.stringify(manifest, null, 4);
1027
- await (0, import_promises2.writeFile)(manifestPath, content, "utf8");
1028
- } catch (error2) {
1029
- throw error2 instanceof Error ? new Error(`Error saving manifest.json: ${error2.message}`) : new Error("Error saving manifest");
1030
- }
1031
- }, update = async (updates) => {
1032
- let updatedManifest = { ...await load(), ...updates };
1033
- return await save(updatedManifest), updatedManifest;
1034
- }, manifestManager = {
1035
- load,
1036
- loadRaw,
1037
- validate,
1038
- save,
1039
- update
1040
- };
1041
-
1042
- // src/core/pluginState/pluginStateManager.ts
1043
- var import_fs3 = require("fs"), import_promises3 = require("fs/promises"), STATE_FILE = ".tracker-plugin", load2 = async () => {
1044
- if (!(0, import_fs3.existsSync)(STATE_FILE))
1045
- return {};
1046
- try {
1047
- let content = await (0, import_promises3.readFile)(STATE_FILE, "utf8");
1048
- return JSON.parse(content);
1049
- } catch {
1050
- return {};
1051
- }
1052
- }, save2 = async (state) => {
1053
- try {
1054
- let content = JSON.stringify(state, null, 4);
1055
- await (0, import_promises3.writeFile)(STATE_FILE, content, "utf8");
1056
- } catch (error2) {
1057
- throw error2 instanceof Error ? new Error(`Error saving ${STATE_FILE}: ${error2.message}`) : new Error(`Error saving ${STATE_FILE}`);
1058
- }
1059
- }, update2 = async (updates) => {
1060
- let updated = { ...await load2(), ...updates };
1061
- return await save2(updated), updated;
1062
- }, pluginStateManager = {
1063
- load: load2,
1064
- save: save2,
1065
- update: update2
1066
- };
1067
-
1068
- // src/commands/catalog/index.ts
1069
- init_logger();
1070
-
1071
- // src/utils/promptPluginMetadata.ts
1072
- var import_prompts2 = require("@inquirer/prompts");
1073
- async function promptPluginName() {
1074
- return (0, import_prompts2.input)({
1075
- message: "Plugin name:",
1076
- validate: (value) => value.trim() ? !0 : "Name is required"
1077
- });
1078
- }
1079
- async function promptPluginDescription() {
1080
- return (0, import_prompts2.input)({
1081
- message: "Plugin description:",
1082
- validate: (value) => value.trim() ? !0 : "Description is required"
1083
- });
1084
- }
1085
- async function promptPluginId(manifest) {
1086
- return (0, import_prompts2.input)({
1087
- message: "Plugin ID:",
1088
- default: manifest.id || void 0,
1089
- validate: (value) => value.trim() ? !0 : "Plugin ID is required"
1090
- });
1091
- }
1092
- async function promptPluginTags() {
1093
- return (await (0, import_prompts2.input)({
1094
- message: "Tags (comma-separated):"
1095
- })).split(",").map((tag) => tag.trim()).filter((tag) => tag.length > 0);
1096
- }
1097
-
1098
- // src/commands/catalog/index.ts
1099
- async function catalog() {
1100
- let manifest = await manifestManager.load(), state = await pluginStateManager.load();
1101
- if (!state.trackerIssueKey) {
1102
- logger_default.warning('Plugin is not registered yet. Run "tracker-cli register" first.'), logger_default.suggest("tracker-cli register");
1103
- return;
1104
- }
1105
- logger_default.newLine(), logger_default.info("\u{1F4DD} Catalog entry information:");
1106
- let name = await promptPluginName(), description = await promptPluginDescription(), tags = await promptPluginTags();
1107
- await pluginStateManager.update({
1108
- catalog: { name, description, tags }
1109
- }), logger_default.newLine(), logger_default.info("\u{1F4E4} Syncing catalog metadata to Tracker issue...");
1110
- let manifestJson = await manifestManager.loadRaw(), issueDescription = buildIssueDescription({
1111
- pluginId: manifest.id ?? "",
1112
- slug: manifest.slug,
1113
- version: manifest.version,
1114
- manifestJson,
1115
- catalogName: name,
1116
- catalogDescription: description,
1117
- catalogTags: tags
1118
- });
1119
- await updatePluginIssue(state.trackerIssueKey, {
1120
- description: issueDescription
1121
- }), logger_default.success("Catalog entry updated successfully!");
1122
- }
1123
-
1124
- // src/commands/create/index.ts
1125
- var path8 = __toESM(require("path")), import_prompts3 = require("@inquirer/prompts");
1126
-
1127
- // src/core/manifest/constants.ts
1128
- var DATA_PERMISSIONS = [
1129
- // top 10
1130
- "tracker:issues:read",
1131
- "tracker:issues:write",
1132
- "tracker:issuetypes:read",
1133
- "tracker:statuses:read",
1134
- "tracker:queues:read",
1135
- "tracker:fields:read",
1136
- "tracker:entities:read",
1137
- "tracker:attachments:read",
1138
- "tracker:attachments:write",
1139
- // in alphabetical order
1140
- "tracker:applications:read",
1141
- "tracker:boards:read",
1142
- "tracker:boards:write",
1143
- "tracker:bulk:read",
1144
- "tracker:bulk:write",
1145
- "tracker:bulkchange:read",
1146
- "tracker:bulkchange:write",
1147
- "tracker:charts:read",
1148
- "tracker:commentTemplates:read",
1149
- "tracker:commentTemplates:write",
1150
- "tracker:components:read",
1151
- "tracker:components:write",
1152
- "tracker:data:read",
1153
- "tracker:departments:read",
1154
- "tracker:entities:write",
1155
- "tracker:externalEventTypes:write",
1156
- "tracker:externalEvents:write",
1157
- "tracker:fields:write",
1158
- "tracker:filterFolders:write",
1159
- "tracker:filters:read",
1160
- "tracker:filters:write",
1161
- "tracker:goals:read",
1162
- "tracker:goals:write",
1163
- "tracker:groups:read",
1164
- "tracker:issueTemplates:read",
1165
- "tracker:issueTemplates:write",
1166
- "tracker:links:read",
1167
- "tracker:linktypes:read",
1168
- "tracker:liveBoards:write",
1169
- "tracker:localFields:read",
1170
- "tracker:maillists:read",
1171
- "tracker:maillists:write",
1172
- "tracker:myself:read",
1173
- "tracker:myself:write",
1174
- "tracker:priorities:read",
1175
- "tracker:queues:write",
1176
- "tracker:reminders:read",
1177
- "tracker:reminders:write",
1178
- "tracker:remotelinks:read",
1179
- "tracker:reports:read",
1180
- "tracker:reports:write",
1181
- "tracker:resolutions:read",
1182
- "tracker:resources:read",
1183
- "tracker:resources:write",
1184
- "tracker:roles:read",
1185
- "tracker:screens:read",
1186
- "tracker:services:read",
1187
- "tracker:sprints:read",
1188
- "tracker:sprints:write",
1189
- "tracker:system:write",
1190
- "tracker:tags:read",
1191
- "tracker:translations:read",
1192
- "tracker:users:read",
1193
- "tracker:users:write",
1194
- "tracker:versions:read",
1195
- "tracker:versions:write",
1196
- "tracker:workflows:read",
1197
- "tracker:workflows:write",
1198
- "tracker:worklog:read",
1199
- "tracker:worklog:write"
1200
- ], TOP_DATA_PERMISSIONS = DATA_PERMISSIONS.slice(0, 10);
1201
- var TEMPLATES = [
1202
- "default",
1203
- "issue.action",
1204
- "issue.block",
1205
- "issue.tab",
1206
- "issue.comment.action",
1207
- "navigation",
1208
- "trigger.action",
1209
- "project.action",
1210
- "portfolio.action",
1211
- "goal.action",
1212
- "issue.floatingbottom.action",
1213
- "attachment.viewer.action"
1214
- ];
1215
- var TEMPLATE_SLOTS = {
1216
- default: [],
1217
- "issue.action": ["issue.action"],
1218
- "issue.block": ["issue.block"],
1219
- "issue.tab": ["issue.tab"],
1220
- "issue.comment.action": ["issue.comment.action"],
1221
- navigation: ["navigation"],
1222
- "trigger.action": ["trigger.create.action", "trigger.edit.action"],
1223
- "project.action": ["project.action"],
1224
- "portfolio.action": ["portfolio.action"],
1225
- "goal.action": ["goal.action"],
1226
- "issue.floatingbottom.action": ["issue.floatingbottom.action"],
1227
- "attachment.viewer.action": ["attachment.viewer.action"]
1228
- };
1229
-
1230
- // src/commands/create/index.ts
1231
- init_logger();
1232
-
1233
- // src/commands/create/utils/getWelcomeMessage.ts
1234
- var import_picocolors2 = __toESM(require("picocolors")), getWelcomeMessage = (pwd) => `
1235
- ${import_picocolors2.default.bold(import_picocolors2.default.white("\u{1F680} Create new tracker plugin"))}
1236
-
1237
- ${import_picocolors2.default.dim(`\u{1F4C1} Creating an app in your current directory: ${pwd}`)}
1238
-
1239
- ${import_picocolors2.default.dim("Press")} ${import_picocolors2.default.yellow("Ctrl+C")} ${import_picocolors2.default.dim("to cancel.")}
1240
- `;
1241
-
1242
- // src/commands/create/utils/initManifest.ts
1243
- var fs2 = __toESM(require("fs")), path3 = __toESM(require("path"));
1244
-
1245
- // src/commands/create/utils/generateManifest.ts
1246
- function buildSlots(template, slotTitle) {
1247
- let slotNames = TEMPLATE_SLOTS[template];
1248
- if (slotNames.length === 0) return;
1249
- let slotEntry = {
1250
- entrypoint: "index.html",
1251
- title: { ru: slotTitle, en: slotTitle }
1252
- };
1253
- return {
1254
- tracker: Object.fromEntries(slotNames.map((slotName) => [slotName, [slotEntry]]))
1255
- };
1256
- }
1257
- var generateManifest = (answers) => ({
1258
- $schema: "./manifest.schema.json",
1259
- slug: answers.packageSlug,
1260
- version: answers.version,
1261
- permissions: answers.permissions,
1262
- slots: buildSlots(answers.template, answers.slotTitle)
1263
- });
1264
-
1265
- // src/commands/create/utils/initManifest.ts
1266
- var initManifest = ({ answers, targetDir }) => {
1267
- let generatedManifest = generateManifest(answers), manifestPath2 = path3.join(targetDir, "manifest.json");
1268
- fs2.writeFileSync(manifestPath2, JSON.stringify(generatedManifest, null, 2), "utf-8");
1269
- };
1270
-
1271
- // src/commands/create/utils/initTemplate.ts
1272
- var fs5 = __toESM(require("fs")), path7 = __toESM(require("path"));
1273
-
1274
- // src/utils/copyDirectory.ts
1275
- var fs3 = __toESM(require("fs")), import_path2 = __toESM(require("path")), import_tinyglobby = require("tinyglobby"), resolveTargetFileName = (file) => file.endsWith(".template") ? "." + file.replace(".template", "") : file, copyDirectory = async (sourceDir, targetDir) => {
1276
- let files = await (0, import_tinyglobby.glob)(["**/*"], {
1277
- cwd: sourceDir,
1278
- onlyFiles: !0,
1279
- dot: !0
1280
- });
1281
- await Promise.all(
1282
- files.map(async (file) => {
1283
- let sourcePath = import_path2.default.join(sourceDir, file), targetFile = resolveTargetFileName(file), targetPath = import_path2.default.join(targetDir, targetFile);
1284
- await fs3.promises.mkdir(import_path2.default.dirname(targetPath), { recursive: !0 }), await fs3.promises.copyFile(sourcePath, targetPath);
1285
- })
1286
- );
1287
- };
1288
-
1289
- // src/utils/getProjectPath.ts
1290
- var import_path3 = __toESM(require("path")), isDevelopment = () => __dirname.includes("/src/"), getProjectPath = (relativePath) => {
1291
- let levelsUp = isDevelopment() ? "../.." : "..";
1292
- return import_path3.default.resolve(__dirname, levelsUp, relativePath);
1293
- }, getTemplatesBaseDir = () => getProjectPath("templates"), getTemplatePath = (filename) => import_path3.default.join(getTemplatesBaseDir(), "_shared", filename);
1294
-
1295
- // src/commands/create/utils/initTemplate.ts
1296
- init_logger();
1297
-
1298
- // src/commands/create/utils/updatePackageJson.ts
1299
- var fs4 = __toESM(require("fs")), path6 = __toESM(require("path")), updatePackageJson = (targetDir, packageSlug) => {
1300
- let packageJsonPath = path6.join(targetDir, "package.json");
1301
- if (fs4.existsSync(packageJsonPath)) {
1302
- let packageJson = JSON.parse(fs4.readFileSync(packageJsonPath, "utf-8"));
1303
- packageJson.name = packageSlug, fs4.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 4) + `
1304
- `);
1085
+ "title"
1086
+ ],
1087
+ properties: {
1088
+ entrypoint: {
1089
+ type: "string",
1090
+ description: "Entry point file path for the slot",
1091
+ minLength: 1
1092
+ },
1093
+ title: {
1094
+ type: "object",
1095
+ description: "Localized titles for the slot",
1096
+ required: [
1097
+ "ru",
1098
+ "en"
1099
+ ],
1100
+ properties: {
1101
+ ru: {
1102
+ type: "string",
1103
+ description: "Russian title",
1104
+ minLength: 1,
1105
+ maxLength: 32
1106
+ },
1107
+ en: {
1108
+ type: "string",
1109
+ description: "English title",
1110
+ minLength: 1,
1111
+ maxLength: 32
1112
+ }
1113
+ },
1114
+ additionalProperties: !1
1115
+ },
1116
+ description: {
1117
+ type: "object",
1118
+ description: "Localized descriptions for the slot",
1119
+ required: [
1120
+ "ru",
1121
+ "en"
1122
+ ],
1123
+ properties: {
1124
+ ru: {
1125
+ type: "string",
1126
+ description: "Russian description",
1127
+ minLength: 1,
1128
+ maxLength: 300
1129
+ },
1130
+ en: {
1131
+ type: "string",
1132
+ description: "English description",
1133
+ minLength: 1,
1134
+ maxLength: 300
1135
+ }
1136
+ },
1137
+ additionalProperties: !1
1138
+ }
1139
+ },
1140
+ additionalProperties: !1
1141
+ }
1305
1142
  }
1306
1143
  };
1307
1144
 
1308
- // src/commands/create/utils/initTemplate.ts
1309
- var initTemplate = async ({ answers, targetDir }) => {
1310
- fs5.existsSync(targetDir) || fs5.mkdirSync(targetDir, { recursive: !0 });
1311
- let templatesBaseDir = getTemplatesBaseDir(), templatesDir = path7.join(templatesBaseDir, answers.template), sharedDir = path7.join(templatesBaseDir, "_shared");
1312
- await copyDirectory(sharedDir, targetDir), await copyDirectory(templatesDir, targetDir), updatePackageJson(targetDir, answers.packageSlug), logger_default.info("Created template with marketplace publication requirements."), logger_default.info(
1313
- "Before publishing, add ./marketplace/index.md, ./marketplace/header-image.jpg (784:325), and ./public/logo.svg (square)."
1314
- );
1145
+ // src/core/manifest/manifestManager.ts
1146
+ var validator = (() => {
1147
+ let ajv = new import_ajv.default({ allErrors: !0, strict: !1 });
1148
+ return (0, import_ajv_formats.default)(ajv), ajv.compile(manifest_schema_default);
1149
+ })(), manifestPath = "./manifest.json", parseManifest = (content) => {
1150
+ let manifest;
1151
+ try {
1152
+ manifest = JSON.parse(content);
1153
+ } catch (error2) {
1154
+ throw error2 instanceof SyntaxError ? new Error(`File manifest.json contains invalid JSON: ${error2.message}`) : new Error(`Error reading manifest.json: ${error2.message}`);
1155
+ }
1156
+ return manifest;
1157
+ }, loadRaw = async () => {
1158
+ if (!(0, import_fs3.existsSync)(manifestPath))
1159
+ throw new Error(`File manifest.json not found at path: ${manifestPath}`);
1160
+ try {
1161
+ return await (0, import_promises.readFile)(manifestPath, "utf8");
1162
+ } catch (error2) {
1163
+ throw error2 instanceof Error ? new Error(`Error reading manifest.json: ${error2.message}`) : new Error("Error loading manifest");
1164
+ }
1165
+ }, load = async () => {
1166
+ let manifestContent = await loadRaw();
1167
+ return parseManifest(manifestContent);
1168
+ }, validate = async () => {
1169
+ let manifest = await load();
1170
+ if (!validator(manifest) && validator.errors) {
1171
+ let errorMessages2 = formatValidationErrors(validator.errors);
1172
+ throw new Error(`Manifest validation failed:
1173
+ ${errorMessages2}`);
1174
+ }
1175
+ }, save = async (manifest) => {
1176
+ try {
1177
+ let content = JSON.stringify(manifest, null, 4);
1178
+ await (0, import_promises.writeFile)(manifestPath, content, "utf8");
1179
+ } catch (error2) {
1180
+ throw error2 instanceof Error ? new Error(`Error saving manifest.json: ${error2.message}`) : new Error("Error saving manifest");
1181
+ }
1182
+ }, update = async (updates) => {
1183
+ let updatedManifest = { ...await load(), ...updates };
1184
+ return await save(updatedManifest), updatedManifest;
1185
+ }, manifestManager = {
1186
+ load,
1187
+ loadRaw,
1188
+ validate,
1189
+ save,
1190
+ update
1315
1191
  };
1316
1192
 
1317
- // src/commands/create/index.ts
1318
- var DEFAULT_VERSION = "0.0.1";
1319
- async function create() {
1320
- logger_default.info(getWelcomeMessage(process.cwd()));
1321
- let packageSlug = await (0, import_prompts3.input)({
1322
- message: "Enter app slug (e.g., my-tracker-plugin):",
1323
- required: !0
1324
- }), template = await (0, import_prompts3.select)({
1325
- message: "Select a template:",
1326
- choices: TEMPLATES.map((templateName) => ({
1327
- name: templateName,
1328
- value: templateName
1329
- }))
1330
- }), dataPermissions = await (0, import_prompts3.checkbox)({
1331
- message: "Select data permissions (some popular options):",
1332
- choices: TOP_DATA_PERMISSIONS.map((permission) => ({
1333
- name: permission,
1334
- value: permission
1335
- }))
1336
- }), answers = {
1337
- packageSlug,
1338
- slotTitle: packageSlug,
1339
- template,
1340
- permissions: {
1341
- data: dataPermissions
1342
- },
1343
- version: DEFAULT_VERSION
1344
- }, targetDir = path8.join(process.cwd(), answers.packageSlug);
1345
- await initTemplate({
1346
- answers,
1347
- targetDir
1348
- }), initManifest({
1349
- answers,
1350
- targetDir
1351
- }), logger_default.newLine(), logger_default.success("Package generated!"), logger_default.commands("To get started, run", [
1352
- `cd ${answers.packageSlug}`,
1353
- "npm install",
1354
- "tracker-cli debug"
1355
- ]);
1356
- }
1357
-
1358
- // src/commands/debug/index.ts
1359
- var import_fs7 = __toESM(require("fs")), import_path7 = __toESM(require("path"));
1360
-
1361
1193
  // src/core/s3config/constants.ts
1362
- var CONFIG_FILE_NAME = "config.json", mimeTypes = {
1194
+ var CONFIG_FILE_NAME = "config.json", DEFAULT_ENVIRONMENT = "production", mimeTypes = {
1363
1195
  ".html": "text/html",
1364
1196
  ".css": "text/css",
1365
1197
  ".js": "text/javascript",
@@ -1390,13 +1222,13 @@ var registerCleanup = (cleanup) => {
1390
1222
  };
1391
1223
 
1392
1224
  // src/commands/debug/utils/createDebugConfig.ts
1393
- var import_fs4 = __toESM(require("fs")), import_path4 = __toESM(require("path"));
1225
+ var import_fs4 = __toESM(require("fs")), import_path5 = __toESM(require("path"));
1394
1226
 
1395
1227
  // src/utils/convertManifestToConfig.ts
1396
- function buildPluginDownloadUrl(baseUrl, id, version) {
1397
- return `${baseUrl.replace(/\/+$/, "")}/${id}/${version}`;
1228
+ function buildPluginDownloadUrl(env, id, version) {
1229
+ return `${getPluginDownloadBase2(env)}/${id}/${version}`;
1398
1230
  }
1399
- var convertManifestToConfig = (manifest, downloadBaseUrl) => {
1231
+ var convertManifestToConfig = (manifest, environment) => {
1400
1232
  let config = {};
1401
1233
  if (!manifest.slots)
1402
1234
  return config;
@@ -1420,7 +1252,7 @@ var convertManifestToConfig = (manifest, downloadBaseUrl) => {
1420
1252
  entrypoint: slotConfig.entrypoint,
1421
1253
  title: slotConfig.title,
1422
1254
  downloadUrl: buildPluginDownloadUrl(
1423
- downloadBaseUrl,
1255
+ environment,
1424
1256
  manifest.slug,
1425
1257
  manifest.version
1426
1258
  )
@@ -1434,13 +1266,13 @@ var convertManifestToConfig = (manifest, downloadBaseUrl) => {
1434
1266
  };
1435
1267
 
1436
1268
  // src/commands/debug/utils/createDebugConfig.ts
1437
- var DEBUG_DOWNLOAD_BASE = "http://localhost:5173/plugins-debug", createDebugConfig = async () => {
1438
- let manifest = await manifestManager.load(), config = convertManifestToConfig(manifest, DEBUG_DOWNLOAD_BASE), configPath = import_path4.default.join(process.cwd(), CONFIG_FILE_NAME);
1269
+ var createDebugConfig = async () => {
1270
+ let manifest = await manifestManager.load(), config = convertManifestToConfig(manifest, "debug"), configPath = import_path5.default.join(process.cwd(), CONFIG_FILE_NAME);
1439
1271
  import_fs4.default.writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8");
1440
1272
  };
1441
1273
 
1442
1274
  // src/commands/debug/utils/lockFile.ts
1443
- var import_fs5 = __toESM(require("fs")), import_path5 = __toESM(require("path")), LOCK_FILE_NAME = ".tracker-cli-debug.lock", getLockFilePath = () => import_path5.default.join(process.cwd(), LOCK_FILE_NAME), getRunningDebugPid = () => {
1275
+ var import_fs5 = __toESM(require("fs")), import_path6 = __toESM(require("path")), LOCK_FILE_NAME = ".tracker-cli-debug.lock", getLockFilePath = () => import_path6.default.join(process.cwd(), LOCK_FILE_NAME), getRunningDebugPid = () => {
1444
1276
  let lockPath = getLockFilePath();
1445
1277
  if (!import_fs5.default.existsSync(lockPath))
1446
1278
  return null;
@@ -1465,10 +1297,10 @@ var import_fs5 = __toESM(require("fs")), import_path5 = __toESM(require("path"))
1465
1297
  };
1466
1298
 
1467
1299
  // src/commands/debug/utils/watchManifest.ts
1468
- var import_fs6 = __toESM(require("fs")), import_path6 = __toESM(require("path"));
1300
+ var import_fs6 = __toESM(require("fs")), import_path7 = __toESM(require("path"));
1469
1301
  init_logger();
1470
1302
  var watchManifest = () => {
1471
- let manifestPath2 = import_path6.default.join(process.cwd(), "manifest.json"), debounceTimer = null, watcher = import_fs6.default.watch(manifestPath2, async (eventType) => {
1303
+ let manifestPath2 = import_path7.default.join(process.cwd(), "manifest.json"), debounceTimer = null, watcher = import_fs6.default.watch(manifestPath2, async (eventType) => {
1472
1304
  eventType === "change" && (debounceTimer && clearTimeout(debounceTimer), debounceTimer = setTimeout(async () => {
1473
1305
  try {
1474
1306
  logger_default.newLine(), logger_default.info("Manifest file changed, validating..."), await manifestManager.validate(), logger_default.info("Regenerating debug config..."), await createDebugConfig(), logger_default.success("Debug config regenerated successfully");
@@ -1492,7 +1324,7 @@ Please stop the existing instance before starting a new one.
1492
1324
  If you lost the terminal tab, you can kill the process with:
1493
1325
  kill ${runningPid}`
1494
1326
  ), process.exit(1));
1495
- let configPath = import_path7.default.join(process.cwd(), CONFIG_FILE_NAME);
1327
+ let configPath = import_path8.default.join(process.cwd(), CONFIG_FILE_NAME);
1496
1328
  options.lint && await manifestManager.validate(), await createDebugConfig();
1497
1329
  let watcher = watchManifest(), cleanup = () => {
1498
1330
  watcher.close(), import_fs7.default.existsSync(configPath) && import_fs7.default.unlinkSync(configPath), removeLockFile();
@@ -1500,16 +1332,21 @@ If you lost the terminal tab, you can kill the process with:
1500
1332
  registerCleanup(cleanup), createLockFile(), await execCommand(pluginCmd.dev), cleanup();
1501
1333
  }
1502
1334
 
1503
- // src/distribution/promptEnvironment.ts
1504
- var impl5 = (init_promptEnvironment(), __toCommonJS(promptEnvironment_exports)), { promptEnvironment: promptEnvironment2 } = impl5;
1505
-
1506
- // src/core/s3config/s3Manager.ts
1507
- var import_fs8 = require("fs"), import_promises4 = require("fs/promises"), os3 = __toESM(require("os")), import_path8 = __toESM(require("path")), import_client_s3 = require("@aws-sdk/client-s3"), import_lib_storage = require("@aws-sdk/lib-storage"), import_tinyglobby2 = require("tinyglobby");
1508
-
1509
- // src/distribution/storage.ts
1510
- var impl6 = (init_storage(), __toCommonJS(storage_exports)), { DEFAULT_ENVIRONMENT: DEFAULT_ENVIRONMENT2, getStorageSettings: getStorageSettings2, listEnvironments: listEnvironments2 } = impl6;
1335
+ // src/core/s3config/promptEnvironment.ts
1336
+ var import_prompts3 = require("@inquirer/prompts");
1337
+ async function promptEnvironment() {
1338
+ return await (0, import_prompts3.select)({
1339
+ message: "Select environment:",
1340
+ choices: [
1341
+ { name: "Testing", value: "testing" },
1342
+ { name: "Production", value: "production" }
1343
+ ],
1344
+ default: "testing"
1345
+ });
1346
+ }
1511
1347
 
1512
1348
  // src/core/s3config/s3Manager.ts
1349
+ var import_fs8 = require("fs"), import_promises2 = require("fs/promises"), os2 = __toESM(require("os")), import_path9 = __toESM(require("path")), import_client_s3 = require("@aws-sdk/client-s3"), import_lib_storage = require("@aws-sdk/lib-storage"), import_tinyglobby2 = require("tinyglobby");
1513
1350
  init_logger();
1514
1351
 
1515
1352
  // src/core/s3config/promptCredentials.ts
@@ -1540,23 +1377,22 @@ var import_prompts4 = require("@inquirer/prompts"), promptS3Credentials = async
1540
1377
  };
1541
1378
 
1542
1379
  // src/core/s3config/s3Manager.ts
1380
+ async function streamToBuffer(body) {
1381
+ if (!body)
1382
+ return Buffer.alloc(0);
1383
+ let chunks = [];
1384
+ for await (let chunk of body)
1385
+ chunks.push(Buffer.from(chunk));
1386
+ return Buffer.concat(chunks);
1387
+ }
1543
1388
  var S3Manager = class {
1544
1389
  constructor() {
1545
- this.credentialsDir = import_path8.default.join(os3.homedir(), ".tracker-cli");
1546
- this.credentialsFile = import_path8.default.join(this.credentialsDir, "s3-credentials.json");
1547
- this._environment = DEFAULT_ENVIRONMENT2;
1548
- this._settings = null;
1549
- }
1550
- async setEnvironment(environment) {
1551
- this._environment = environment, this._settings = await getStorageSettings2(environment), logger_default.info(`Environment set to: ${environment}`);
1552
- }
1553
- getEnvironment() {
1554
- return this._environment;
1390
+ this.credentialsDir = import_path9.default.join(os2.homedir(), ".tracker-cli");
1391
+ this.credentialsFile = import_path9.default.join(this.credentialsDir, "s3-credentials.json");
1392
+ this._environment = DEFAULT_ENVIRONMENT;
1555
1393
  }
1556
- getSettings() {
1557
- if (!this._settings)
1558
- throw new Error("Storage settings not loaded. Call s3Manager.setEnvironment() first.");
1559
- return this._settings;
1394
+ setEnvironment(environment) {
1395
+ this._environment = environment, logger_default.info(`Environment set to: ${environment}`);
1560
1396
  }
1561
1397
  async checkS3Auth() {
1562
1398
  await this.checkAuth("s3");
@@ -1565,7 +1401,7 @@ var S3Manager = class {
1565
1401
  await this.checkAuth("mds");
1566
1402
  }
1567
1403
  async saveCredentials(credentials) {
1568
- (0, import_fs8.existsSync)(this.credentialsDir) || (0, import_fs8.mkdirSync)(this.credentialsDir, { recursive: !0, mode: 448 }), await (0, import_promises4.writeFile)(this.credentialsFile, JSON.stringify(credentials, null, 2), {
1404
+ (0, import_fs8.existsSync)(this.credentialsDir) || (0, import_fs8.mkdirSync)(this.credentialsDir, { recursive: !0, mode: 448 }), await (0, import_promises2.writeFile)(this.credentialsFile, JSON.stringify(credentials, null, 2), {
1569
1405
  mode: 384
1570
1406
  });
1571
1407
  }
@@ -1580,21 +1416,18 @@ var S3Manager = class {
1580
1416
  }
1581
1417
  }
1582
1418
  async getPluginsConfig() {
1583
- let s3Client = this.createMdsS3Client(), bucket = this.getSettings().configBucket;
1419
+ let s3Client = this.createMdsS3Client(), bucket = this.getConfigBucket();
1584
1420
  try {
1585
- let { Body } = await s3Client.send(
1421
+ let response = await s3Client.send(
1586
1422
  new import_client_s3.GetObjectCommand({ Bucket: bucket, Key: CONFIG_FILE_NAME })
1587
- );
1588
- if (!Body)
1589
- return logger_default.info("NO DATA"), {};
1590
- let text = await Body.transformToString("utf8");
1591
- return JSON.parse(text);
1423
+ ), body = await streamToBuffer(response.Body);
1424
+ return body.length ? JSON.parse(body.toString("utf8")) : (logger_default.info("NO DATA"), {});
1592
1425
  } catch (error2) {
1593
1426
  return logger_default.error("Error getting plugins config from S3", error2), process.exit();
1594
1427
  }
1595
1428
  }
1596
1429
  async savePluginsConfig(config) {
1597
- let s3Client = this.createMdsS3Client(), bucket = this.getSettings().configBucket;
1430
+ let s3Client = this.createMdsS3Client(), bucket = this.getConfigBucket();
1598
1431
  try {
1599
1432
  await new import_lib_storage.Upload({
1600
1433
  client: s3Client,
@@ -1614,11 +1447,11 @@ var S3Manager = class {
1614
1447
  logger_default.warning("No files to upload in dist folder");
1615
1448
  return;
1616
1449
  }
1617
- let keyPrefix = `${manifest.slug}/${manifest.version}/`, s3Client = this.createAwsS3Client(), distPath = import_path8.default.resolve("./dist"), bucket = this.getSettings().staticBucket;
1450
+ let keyPrefix = `${manifest.slug}/${manifest.version}/`, s3Client = this.createAwsS3Client(), distPath = import_path9.default.resolve("./dist"), bucket = this.getStaticBucket();
1618
1451
  logger_default.info(`\u{1F4E6} Starting file upload from ${distPath} to S3 bucket: ${bucket}`);
1619
1452
  for (let filePath of files)
1620
1453
  try {
1621
- let fileContent = await (0, import_promises4.readFile)(filePath), relativePath = import_path8.default.relative(distPath, filePath), s3Key = keyPrefix + relativePath.replace(/\\/g, "/"), mimeType = this.getMimeType(filePath);
1454
+ let fileContent = await (0, import_promises2.readFile)(filePath), relativePath = import_path9.default.relative(distPath, filePath), s3Key = keyPrefix + relativePath.replace(/\\/g, "/"), mimeType = this.getMimeType(filePath);
1622
1455
  logger_default.info(`\u2B06\uFE0F Uploading: ${relativePath}`), await new import_lib_storage.Upload({
1623
1456
  client: s3Client,
1624
1457
  params: {
@@ -1639,15 +1472,15 @@ var S3Manager = class {
1639
1472
  * @param rootPaths paths relative to cwd, e.g. ['manifest.json', 'marketplace']
1640
1473
  */
1641
1474
  async uploadRootFiles(manifest, rootPaths) {
1642
- let keyPrefix = `${manifest.slug}/${manifest.version}/`, s3Client = this.createAwsS3Client(), bucket = this.getSettings().staticBucket, cwd = process.cwd();
1475
+ let keyPrefix = `${manifest.slug}/${manifest.version}/`, s3Client = this.createAwsS3Client(), bucket = this.getStaticBucket(), cwd = process.cwd();
1643
1476
  for (let rootPath of rootPaths) {
1644
- let absPath = import_path8.default.resolve(cwd, rootPath);
1477
+ let absPath = import_path9.default.resolve(cwd, rootPath);
1645
1478
  if (!(0, import_fs8.existsSync)(absPath)) {
1646
1479
  logger_default.warning(`Skipping "${rootPath}" \u2014 not found`);
1647
1480
  continue;
1648
1481
  }
1649
1482
  if ((await import("fs")).statSync(absPath).isFile()) {
1650
- let fileContent = await (0, import_promises4.readFile)(absPath), s3Key = keyPrefix + import_path8.default.basename(absPath), mimeType = this.getMimeType(absPath);
1483
+ let fileContent = await (0, import_promises2.readFile)(absPath), s3Key = keyPrefix + import_path9.default.basename(absPath), mimeType = this.getMimeType(absPath);
1651
1484
  logger_default.info(`\u2B06\uFE0F Uploading: ${rootPath}`), await new import_lib_storage.Upload({
1652
1485
  client: s3Client,
1653
1486
  params: {
@@ -1665,7 +1498,7 @@ var S3Manager = class {
1665
1498
  });
1666
1499
  for (let filePath of files)
1667
1500
  try {
1668
- let fileContent = await (0, import_promises4.readFile)(filePath), relativePath = import_path8.default.relative(cwd, filePath), s3Key = keyPrefix + relativePath.replace(/\\/g, "/"), mimeType = this.getMimeType(filePath);
1501
+ let fileContent = await (0, import_promises2.readFile)(filePath), relativePath = import_path9.default.relative(cwd, filePath), s3Key = keyPrefix + relativePath.replace(/\\/g, "/"), mimeType = this.getMimeType(filePath);
1669
1502
  logger_default.info(`\u2B06\uFE0F Uploading: ${relativePath}`), await new import_lib_storage.Upload({
1670
1503
  client: s3Client,
1671
1504
  params: {
@@ -1682,10 +1515,10 @@ var S3Manager = class {
1682
1515
  }
1683
1516
  }
1684
1517
  createAwsS3Client() {
1685
- let credentials = this.credentials, settings = this.getSettings();
1518
+ let credentials = this.credentials;
1686
1519
  return (!credentials?.s3AccessKeyId || !credentials?.s3SecretAccessKey) && (logger_default.error("S3 credentials not found. Please log in using: tracker-cli login"), process.exit()), new import_client_s3.S3Client({
1687
- region: settings.awsRegion,
1688
- endpoint: settings.awsEndpoint,
1520
+ region: getS3Region2(),
1521
+ endpoint: getS3Endpoint2(),
1689
1522
  credentials: {
1690
1523
  accessKeyId: credentials.s3AccessKeyId,
1691
1524
  secretAccessKey: credentials.s3SecretAccessKey
@@ -1700,10 +1533,9 @@ var S3Manager = class {
1700
1533
  if (!creds || !creds.s3AccessKeyId || !creds.s3SecretAccessKey)
1701
1534
  return !1;
1702
1535
  try {
1703
- let settings = await this.resolveSettings();
1704
1536
  return await new import_client_s3.S3Client({
1705
- region: settings.awsRegion,
1706
- endpoint: settings.awsEndpoint,
1537
+ region: getS3Region2(),
1538
+ endpoint: getS3Endpoint2(),
1707
1539
  credentials: {
1708
1540
  accessKeyId: creds.s3AccessKeyId,
1709
1541
  secretAccessKey: creds.s3SecretAccessKey
@@ -1718,18 +1550,8 @@ var S3Manager = class {
1718
1550
  if (!creds || !creds.mdsAccessKeyId || !creds.mdsSecretAccessKey)
1719
1551
  return !1;
1720
1552
  try {
1721
- let settings = await this.resolveSettings();
1722
- return await new import_client_s3.S3Client({
1723
- region: settings.awsRegion,
1724
- endpoint: settings.mdsEndpoint,
1725
- forcePathStyle: !0,
1726
- credentials: {
1727
- accessKeyId: creds.mdsAccessKeyId,
1728
- secretAccessKey: creds.mdsSecretAccessKey
1729
- }
1730
- }).send(
1731
- new import_client_s3.GetObjectCommand({ Bucket: settings.configBucket, Key: CONFIG_FILE_NAME })
1732
- ), !0;
1553
+ let mdsClient = this.buildMdsS3Client(creds), bucket = this.getConfigBucket();
1554
+ return await mdsClient.send(new import_client_s3.GetObjectCommand({ Bucket: bucket, Key: CONFIG_FILE_NAME })), !0;
1733
1555
  } catch (error2) {
1734
1556
  return logger_default.error("MDS credential validation failed:", error2), !1;
1735
1557
  }
@@ -1760,11 +1582,20 @@ var S3Manager = class {
1760
1582
  }
1761
1583
  await this.saveCredentials(updatedCreds), logger_default.success(`${type.toUpperCase()} credentials saved successfully.`);
1762
1584
  }
1585
+ getConfigBucket() {
1586
+ return getBuckets2()[this._environment].config;
1587
+ }
1588
+ getStaticBucket() {
1589
+ return getBuckets2()[this._environment].static;
1590
+ }
1763
1591
  createMdsS3Client() {
1764
- let credentials = this.credentials, settings = this.getSettings();
1765
- return (!credentials?.mdsAccessKeyId || !credentials?.mdsSecretAccessKey) && (logger_default.error("MdsS3 credentials not found. Please log in using: tracker-cli login"), process.exit()), new import_client_s3.S3Client({
1766
- region: settings.awsRegion,
1767
- endpoint: settings.mdsEndpoint,
1592
+ let credentials = this.credentials;
1593
+ return (!credentials?.mdsAccessKeyId || !credentials?.mdsSecretAccessKey) && (logger_default.error("MdsS3 credentials not found. Please log in using: tracker-cli login"), process.exit()), this.buildMdsS3Client(credentials);
1594
+ }
1595
+ buildMdsS3Client(credentials) {
1596
+ return new import_client_s3.S3Client({
1597
+ region: getS3Region2(),
1598
+ endpoint: getMdsEndpoint2(),
1768
1599
  forcePathStyle: !0,
1769
1600
  credentials: {
1770
1601
  accessKeyId: credentials.mdsAccessKeyId,
@@ -1772,37 +1603,192 @@ var S3Manager = class {
1772
1603
  }
1773
1604
  });
1774
1605
  }
1775
- async resolveSettings() {
1776
- return this._settings ? this._settings : (this._settings = await getStorageSettings2(this._environment), this._settings);
1777
- }
1778
1606
  getMimeType(filePath) {
1779
- let ext = import_path8.default.extname(filePath).toLowerCase();
1607
+ let ext = import_path9.default.extname(filePath).toLowerCase();
1780
1608
  return mimeTypes[ext] || "application/octet-stream";
1781
1609
  }
1782
1610
  }, s3Manager = new S3Manager();
1783
1611
 
1784
- // src/utils/getFiles.ts
1785
- var import_fs9 = __toESM(require("fs")), import_path9 = __toESM(require("path")), import_tinyglobby3 = require("tinyglobby");
1786
- init_logger();
1787
- var getFiles = async () => {
1788
- let distPath = import_path9.default.resolve("./dist");
1789
- import_fs9.default.existsSync(distPath) || (logger_default.error("Dist folder not found. Build the project first"), process.exit());
1790
- let files = await (0, import_tinyglobby3.glob)(["**/*"], {
1791
- cwd: distPath,
1792
- onlyFiles: !0,
1793
- absolute: !0
1612
+ // src/utils/getFiles.ts
1613
+ var import_fs9 = __toESM(require("fs")), import_path10 = __toESM(require("path")), import_tinyglobby3 = require("tinyglobby");
1614
+ init_logger();
1615
+ var getFiles = async () => {
1616
+ let distPath = import_path10.default.resolve("./dist");
1617
+ import_fs9.default.existsSync(distPath) || (logger_default.error("Dist folder not found. Build the project first"), process.exit());
1618
+ let files = await (0, import_tinyglobby3.glob)(["**/*"], {
1619
+ cwd: distPath,
1620
+ onlyFiles: !0,
1621
+ absolute: !0
1622
+ });
1623
+ return logger_default.info(`\u{1F4C1} Found ${files.length} files to upload`), files;
1624
+ };
1625
+
1626
+ // src/commands/deploy/index.ts
1627
+ async function deploy() {
1628
+ await s3Manager.checkS3Auth();
1629
+ let environment = await promptEnvironment();
1630
+ s3Manager.setEnvironment(environment);
1631
+ let manifest = await manifestManager.load();
1632
+ await build({ environment });
1633
+ let files = await getFiles();
1634
+ await s3Manager.uploadPluginFiles(manifest, files);
1635
+ }
1636
+
1637
+ // src/api/tracker.ts
1638
+ var import_form_data = __toESM(require("form-data"));
1639
+
1640
+ // src/api/trackerApiRequest.ts
1641
+ var import_node_fetch2 = __toESM(require("node-fetch"));
1642
+
1643
+ // src/core/auth/authManager.ts
1644
+ var fs11 = __toESM(require("fs")), os3 = __toESM(require("os")), path15 = __toESM(require("path")), authDir = path15.join(os3.homedir(), ".tracker-cli"), authFile = path15.join(authDir, "auth.json"), getData = () => {
1645
+ try {
1646
+ if (!fs11.existsSync(authFile))
1647
+ return null;
1648
+ let data = fs11.readFileSync(authFile, "utf-8");
1649
+ return JSON.parse(data);
1650
+ } catch {
1651
+ return null;
1652
+ }
1653
+ }, getAuthHeader = () => {
1654
+ let authData = getData();
1655
+ return authData ? `OAuth ${authData.token}` : null;
1656
+ }, saveAuthData = (token) => {
1657
+ let authData = {
1658
+ token
1659
+ };
1660
+ fs11.existsSync(authDir) || fs11.mkdirSync(authDir, { recursive: !0, mode: 448 }), fs11.writeFileSync(authFile, JSON.stringify(authData, null, 2), {
1661
+ mode: 384
1662
+ });
1663
+ }, removeAuthData = () => {
1664
+ fs11.existsSync(authFile) && fs11.unlinkSync(authFile);
1665
+ }, authManager = {
1666
+ get authHeader() {
1667
+ return getAuthHeader();
1668
+ },
1669
+ get data() {
1670
+ return getData();
1671
+ },
1672
+ saveAuthData,
1673
+ removeAuthData
1674
+ };
1675
+
1676
+ // src/api/trackerApiRequest.ts
1677
+ function buildQueryString(queryParams) {
1678
+ if (!queryParams || Object.keys(queryParams).length === 0)
1679
+ return "";
1680
+ let params = new URLSearchParams();
1681
+ return Object.entries(queryParams).forEach(([key, value]) => {
1682
+ params.append(key, String(value));
1683
+ }), `?${params.toString()}`;
1684
+ }
1685
+ async function parseResponse(response) {
1686
+ return response.status === 204 || response.headers.get("content-length") === "0" ? void 0 : response.headers.get("content-type")?.includes("application/json") ? await response.json() : await response.text();
1687
+ }
1688
+ async function trackerApiRequest(endpoint, options = {}) {
1689
+ let { method = "GET", body, headers = {}, formData, queryParams } = options, authHeader = authManager.authHeader;
1690
+ if (!authHeader)
1691
+ throw new Error('Not authenticated. Run "tracker-cli login" first.');
1692
+ let requestHeaders = {
1693
+ Authorization: authHeader,
1694
+ Accept: "application/json",
1695
+ ...headers
1696
+ }, requestBody;
1697
+ if (formData) {
1698
+ let formHeaders = formData.getHeaders?.();
1699
+ Object.assign(requestHeaders, formHeaders), requestBody = formData;
1700
+ } else body && method !== "GET" && (requestHeaders["Content-Type"] = "application/json", requestBody = JSON.stringify(body));
1701
+ let queryString = buildQueryString(queryParams), url = `${getTrackerBaseUrl2()}${endpoint}${queryString}`, response = await (0, import_node_fetch2.default)(url, {
1702
+ method,
1703
+ headers: requestHeaders,
1704
+ body: requestBody
1705
+ });
1706
+ if (!response.ok) {
1707
+ let errorText = await response.text(), errorMessage = `Tracker API error ${response.status}: ${errorText}`;
1708
+ try {
1709
+ let errorJson = JSON.parse(errorText);
1710
+ errorJson.message && (errorMessage = `Tracker API error ${response.status}: ${errorJson.message}`);
1711
+ } catch {
1712
+ }
1713
+ throw new Error(errorMessage);
1714
+ }
1715
+ return parseResponse(response);
1716
+ }
1717
+
1718
+ // src/api/tracker.ts
1719
+ var PLUGINMOD_QUEUE = "PLUGINMOD";
1720
+ function buildIssueSummary(slug) {
1721
+ return `Plugin: ${slug}`;
1722
+ }
1723
+ function buildIssueDescription(params) {
1724
+ let { pluginId, slug, version, manifestJson, catalogName, catalogDescription, catalogTags } = params, lines = [
1725
+ `**Plugin UUID:** ${pluginId}`,
1726
+ `**Slug:** ${slug}`,
1727
+ `**Version:** ${version}`
1728
+ ];
1729
+ return catalogName && lines.push(`**Name:** ${catalogName}`), catalogDescription && lines.push(`**Description:** ${catalogDescription}`), catalogTags && catalogTags.length > 0 && lines.push(`**Tags:** ${catalogTags.join(", ")}`), lines.push("", "**Manifest:**", "```json", manifestJson, "```"), lines.join(`
1730
+ `);
1731
+ }
1732
+ async function createPluginIssue(params) {
1733
+ let { slug, pluginId, version, manifestJson } = params, body = {
1734
+ queue: PLUGINMOD_QUEUE,
1735
+ summary: buildIssueSummary(slug),
1736
+ description: buildIssueDescription({ pluginId, slug, version, manifestJson }),
1737
+ tags: ["tracker-plugin"]
1738
+ };
1739
+ return trackerApiRequest("/issues", {
1740
+ method: "POST",
1741
+ body
1742
+ });
1743
+ }
1744
+ async function updatePluginIssue(issueKey, params) {
1745
+ return trackerApiRequest(`/issues/${issueKey}`, {
1746
+ method: "PATCH",
1747
+ body: params
1748
+ });
1749
+ }
1750
+ async function attachFileToIssue(issueKey, fileBuffer, fileName) {
1751
+ let formData = new import_form_data.default();
1752
+ return formData.append("file", fileBuffer, {
1753
+ filename: fileName,
1754
+ contentType: "application/zip"
1755
+ }), trackerApiRequest(`/issues/${issueKey}/attachments`, {
1756
+ method: "POST",
1757
+ formData
1758
+ });
1759
+ }
1760
+ async function getIssueTransitions(issueKey) {
1761
+ return trackerApiRequest(`/issues/${issueKey}/transitions`, {
1762
+ method: "GET"
1763
+ });
1764
+ }
1765
+ async function executeIssueTransition(issueKey, transitionId) {
1766
+ await trackerApiRequest(`/issues/${issueKey}/transitions/${transitionId}/_execute`, {
1767
+ method: "POST",
1768
+ body: {}
1769
+ });
1770
+ }
1771
+ async function getIssue(issueKey) {
1772
+ return trackerApiRequest(`/issues/${issueKey}`, {
1773
+ method: "GET"
1774
+ });
1775
+ }
1776
+ async function getMyPluginIssues() {
1777
+ return trackerApiRequest("/issues/_search", {
1778
+ method: "POST",
1779
+ body: {
1780
+ filter: {
1781
+ queue: PLUGINMOD_QUEUE,
1782
+ createdBy: "me()"
1783
+ }
1784
+ }
1785
+ });
1786
+ }
1787
+ async function addIssueComment(issueKey, text) {
1788
+ await trackerApiRequest(`/issues/${issueKey}/comments`, {
1789
+ method: "POST",
1790
+ body: { text }
1794
1791
  });
1795
- return logger_default.info(`\u{1F4C1} Found ${files.length} files to upload`), files;
1796
- };
1797
-
1798
- // src/commands/deploy/index.ts
1799
- async function deploy() {
1800
- let environment = await promptEnvironment2();
1801
- await s3Manager.setEnvironment(environment), await s3Manager.checkS3Auth();
1802
- let manifest = await manifestManager.load();
1803
- await build({ environment });
1804
- let files = await getFiles();
1805
- await s3Manager.uploadPluginFiles(manifest, files);
1806
1792
  }
1807
1793
 
1808
1794
  // src/api/utils.ts
@@ -1837,6 +1823,32 @@ function mapTrackerStatusToPluginStatus(issue) {
1837
1823
  }
1838
1824
  }
1839
1825
 
1826
+ // src/core/pluginState/pluginStateManager.ts
1827
+ var import_fs10 = require("fs"), import_promises3 = require("fs/promises"), STATE_FILE = ".tracker-plugin", load2 = async () => {
1828
+ if (!(0, import_fs10.existsSync)(STATE_FILE))
1829
+ return {};
1830
+ try {
1831
+ let content = await (0, import_promises3.readFile)(STATE_FILE, "utf8");
1832
+ return JSON.parse(content);
1833
+ } catch {
1834
+ return {};
1835
+ }
1836
+ }, save2 = async (state) => {
1837
+ try {
1838
+ let content = JSON.stringify(state, null, 4);
1839
+ await (0, import_promises3.writeFile)(STATE_FILE, content, "utf8");
1840
+ } catch (error2) {
1841
+ throw error2 instanceof Error ? new Error(`Error saving ${STATE_FILE}: ${error2.message}`) : new Error(`Error saving ${STATE_FILE}`);
1842
+ }
1843
+ }, update2 = async (updates) => {
1844
+ let updated = { ...await load2(), ...updates };
1845
+ return await save2(updated), updated;
1846
+ }, pluginStateManager = {
1847
+ load: load2,
1848
+ save: save2,
1849
+ update: update2
1850
+ };
1851
+
1840
1852
  // src/commands/info/index.ts
1841
1853
  init_logger();
1842
1854
 
@@ -1860,9 +1872,8 @@ var printHeader = (title) => {
1860
1872
  console.info(` ${import_picocolors3.default.dim(label.padEnd(minWidth))} ${value}`);
1861
1873
  }, printPluginId = (pluginId) => {
1862
1874
  printField("Plugin ID", import_picocolors3.default.cyan(pluginId));
1863
- }, printVersionId = (versionId) => {
1864
- printField("Version ID", import_picocolors3.default.cyan(versionId));
1865
- }, printStatus = (status) => {
1875
+ };
1876
+ var printStatus = (status) => {
1866
1877
  printField("Status", getStatusBadge(status.toUpperCase()));
1867
1878
  }, printCreated = (createdAt) => {
1868
1879
  printField("Created", import_picocolors3.default.white(formatDate(createdAt)));
@@ -1881,23 +1892,9 @@ function showPluginVersions(versions2) {
1881
1892
  console.info(` ${import_picocolors3.default.bold(String(index + 1) + ".")} ${import_picocolors3.default.cyan(version.versionId)}`), printField("Status", getStatusBadge(version.status.toUpperCase())), printField("Version", import_picocolors3.default.white(version.version)), printCreated(version.createdAt), logger_default.newLine();
1882
1893
  });
1883
1894
  }
1884
- function showPluginList(plugins) {
1885
- if (!plugins || plugins.length === 0) {
1886
- console.info(import_picocolors3.default.dim(" No plugins found")), logger_default.newLine();
1887
- return;
1888
- }
1889
- printHeader(`\u{1F4CB} Your Plugins \xB7 ${plugins.length}`), logger_default.newLine(), plugins.forEach((plugin) => {
1890
- console.info(
1891
- ` ${import_picocolors3.default.magenta(plugin.slug)} ${import_picocolors3.default.dim("\xB7")} ${getStatusBadge(plugin.status.toUpperCase())} ${import_picocolors3.default.dim("\xB7")} ${import_picocolors3.default.cyan(plugin.id)}`
1892
- ), logger_default.newLine();
1893
- });
1894
- }
1895
1895
  function showPluginCreated(data) {
1896
1896
  printSuccessHeader("\u2705 Plugin Created"), logger_default.newLine(), printPluginId(data.id);
1897
1897
  }
1898
- function showPluginSubmitted(data) {
1899
- printSuccessHeader("\u{1F680} Plugin Submitted"), logger_default.newLine(), printPluginId(data.pluginId), printVersionId(data.versionId), printStatus(data.status);
1900
- }
1901
1898
  function showTrackerPluginList(issues, mapStatus) {
1902
1899
  if (!issues || issues.length === 0) {
1903
1900
  console.info(import_picocolors3.default.dim(" No plugins found")), logger_default.newLine();
@@ -1959,12 +1956,13 @@ var mergeConfigs = (config, newConfig) => {
1959
1956
 
1960
1957
  // src/commands/install/index.ts
1961
1958
  async function install() {
1962
- let environment = await promptEnvironment2();
1963
- await s3Manager.setEnvironment(environment), await s3Manager.checkMdsAuth();
1959
+ await s3Manager.checkMdsAuth();
1960
+ let environment = await promptEnvironment();
1961
+ s3Manager.setEnvironment(environment);
1964
1962
  let [config, manifest] = await Promise.all([
1965
1963
  s3Manager.getPluginsConfig(),
1966
1964
  manifestManager.load()
1967
- ]), settings = s3Manager.getSettings(), downloadBaseUrl = `${settings.awsEndpoint.replace(/\/+$/, "")}/${settings.staticBucket}`, pluginConfig = convertManifestToConfig(manifest, downloadBaseUrl), mergedConfig = mergeConfigs(config, pluginConfig);
1965
+ ]), pluginConfig = convertManifestToConfig(manifest, environment), mergedConfig = mergeConfigs(config, pluginConfig);
1968
1966
  await s3Manager.savePluginsConfig(mergedConfig), logger_default.info("\u{1F389} Installation completed successfully!");
1969
1967
  }
1970
1968
 
@@ -2000,7 +1998,7 @@ async function apiRequest(endpoint, options = {}) {
2000
1998
  headers: requestHeaders
2001
1999
  };
2002
2000
  body && method !== "GET" && (requestOptions.body = JSON.stringify(body));
2003
- let url = `${await getTrackerApiUrl2()}${endpoint}`, response = await fetch(url, requestOptions);
2001
+ let url = `${getTrackerBaseUrl2()}${endpoint}`, response = await fetch(url, requestOptions);
2004
2002
  if (!response.ok)
2005
2003
  throw new Error([response.status, response.statusText].join(" ").trim());
2006
2004
  let contentType = response.headers.get("content-type");
@@ -2020,13 +2018,13 @@ init_logger();
2020
2018
 
2021
2019
  // src/commands/login/utils/promptForCredentials.ts
2022
2020
  var import_prompts5 = require("@inquirer/prompts"), import_picocolors4 = __toESM(require("picocolors"));
2023
- var OSC = "\x1B]", BEL = "\x07", promptForCredentials = async () => {
2024
- let tokenUrl = await getOAuthTokenUrl2();
2025
- return tokenUrl && (console.info(
2026
- import_picocolors4.default.dim("Get your OAuth token at: ") + import_picocolors4.default.cyan(`${OSC}8;;${tokenUrl}${BEL}${tokenUrl}${OSC}8;;${BEL}`)
2027
- ), console.info("")), {
2021
+ var ESC = "\x1B", BEL = "\x07", promptForCredentials = async () => {
2022
+ let tokenUrl = getOAuthTokenUrl2();
2023
+ return console.info(
2024
+ import_picocolors4.default.dim("Get your OAuth token at: ") + import_picocolors4.default.cyan(`${ESC}]8;;${tokenUrl}${BEL}${tokenUrl}${ESC}]8;;${BEL}`)
2025
+ ), console.info(""), {
2028
2026
  token: (await (0, import_prompts5.input)({
2029
- message: "Enter your OAuth token:",
2027
+ message: "Enter your Yandex Tracker OAuth token:",
2030
2028
  required: !0
2031
2029
  })).trim()
2032
2030
  };
@@ -2058,66 +2056,10 @@ function logout() {
2058
2056
  }
2059
2057
 
2060
2058
  // src/commands/register/index.ts
2061
- var import_crypto = require("crypto");
2062
2059
  init_logger();
2063
- async function register() {
2064
- let manifest = await manifestManager.load(), state = await pluginStateManager.load();
2065
- if (state.trackerIssueKey)
2066
- try {
2067
- let issue2 = await getIssue(state.trackerIssueKey);
2068
- logger_default.error(
2069
- `Plugin is already registered. Tracker issue: ${issue2.key} (${issue2.summary})`
2070
- ), process.exit(1);
2071
- } catch {
2072
- }
2073
- let pluginId = manifest.id ?? (0, import_crypto.randomUUID)();
2074
- logger_default.info("\u23F3 Creating plugin issue in Tracker..."), manifest.id || await manifestManager.update({ id: pluginId });
2075
- let manifestJson = await manifestManager.loadRaw(), issue = await createPluginIssue({
2076
- pluginId,
2077
- slug: manifest.slug,
2078
- version: manifest.version,
2079
- manifestJson
2080
- });
2081
- await pluginStateManager.update({ trackerIssueKey: issue.key }), logger_default.success("Plugin registered!"), logger_default.info(` Tracker issue : ${issue.key}`), logger_default.info(` Summary : ${issue.summary}`), logger_default.info(` Plugin UUID : ${pluginId}`), logger_default.commands("\u{1F4A1} Next steps", [
2082
- "tracker-cli catalog # Update catalog metadata",
2083
- "tracker-cli upload # Build and upload plugin bundle",
2084
- "tracker-cli submit # Submit for review"
2085
- ]);
2086
- }
2087
2060
 
2088
2061
  // src/commands/submit/index.ts
2089
2062
  init_logger();
2090
- var SUBMIT_TRANSITION_DISPLAY = "waiting for approve";
2091
- async function submit() {
2092
- let manifest = await manifestManager.load(), state = await pluginStateManager.load();
2093
- if (!state.trackerIssueKey) {
2094
- logger_default.warning('Plugin is not registered yet. Run "tracker-cli register" first.'), logger_default.suggest("tracker-cli register");
2095
- return;
2096
- }
2097
- let issueKey = state.trackerIssueKey;
2098
- logger_default.info(`\u23F3 Checking for uploaded bundle on ${issueKey}...`);
2099
- let attachments = await getIssueAttachments(issueKey), archiveName = `${manifest.slug}-${manifest.version}.zip`;
2100
- if (!attachments.some((a) => a.name === archiveName)) {
2101
- logger_default.warning(
2102
- `No bundle found for version ${manifest.version}. Upload it first with "tracker-cli upload".`
2103
- ), logger_default.suggest("tracker-cli upload");
2104
- return;
2105
- }
2106
- logger_default.info(`\u23F3 Fetching available transitions for ${issueKey}...`);
2107
- let transitions = await getIssueTransitions(issueKey), submitTransition = transitions.find(
2108
- (t) => t.display?.toLowerCase().includes(SUBMIT_TRANSITION_DISPLAY) || t.id?.toLowerCase().includes("waitingforapprove") || t.id?.toLowerCase().includes("waiting")
2109
- );
2110
- if (!submitTransition) {
2111
- let available = transitions.map((t) => `"${t.display ?? t.id}"`).join(", ");
2112
- logger_default.error(
2113
- `Could not find a "Waiting for approve" transition on ${issueKey}. Available: ${available}`
2114
- );
2115
- return;
2116
- }
2117
- logger_default.info(
2118
- `\u{1F680} Submitting plugin for review (transition: "${submitTransition.display ?? submitTransition.id}")...`
2119
- ), await executeIssueTransition(issueKey, submitTransition.id), await addIssueComment(issueKey, `Submitted version \`${manifest.version}\` for review.`), logger_default.success("Plugin submitted for review!"), logger_default.info(` Tracker issue : ${issueKey}`), logger_default.info(` Version : ${manifest.version}`);
2120
- }
2121
2063
 
2122
2064
  // src/commands/uninstall/index.ts
2123
2065
  init_logger();
@@ -2136,8 +2078,7 @@ var removePluginFromConfig = (config, pluginSlug) => {
2136
2078
 
2137
2079
  // src/commands/uninstall/index.ts
2138
2080
  async function uninstall() {
2139
- let environment = await promptEnvironment2();
2140
- await s3Manager.setEnvironment(environment), await s3Manager.checkMdsAuth();
2081
+ await s3Manager.checkMdsAuth();
2141
2082
  let [config, manifest] = await Promise.all([
2142
2083
  s3Manager.getPluginsConfig(),
2143
2084
  manifestManager.load()
@@ -2159,26 +2100,32 @@ async function uninstall() {
2159
2100
  }
2160
2101
 
2161
2102
  // src/commands/up/utils/copyTemplateFiles.ts
2162
- var import_fs10 = require("fs"), import_promises5 = require("fs/promises");
2103
+ var import_fs11 = require("fs"), import_promises4 = require("fs/promises");
2163
2104
  var copyTemplateFiles = async (filenames) => {
2164
2105
  await Promise.all(
2165
2106
  filenames.map(async (filename) => {
2166
2107
  let templatePath = getTemplatePath(filename), targetPath = `./${filename}`;
2167
- (0, import_fs10.existsSync)(templatePath) && (0, import_fs10.existsSync)(targetPath) && await (0, import_promises5.copyFile)(templatePath, targetPath);
2108
+ (0, import_fs11.existsSync)(templatePath) && (0, import_fs11.existsSync)(targetPath) && await (0, import_promises4.copyFile)(templatePath, targetPath);
2168
2109
  })
2169
2110
  );
2170
2111
  };
2171
2112
 
2113
+ // src/commands/up/utils/updateManifestCompatibility.ts
2114
+ async function updateManifestCompatibility() {
2115
+ let manifest = await manifestManager.load(), updates = {};
2116
+ "manifest_version" in manifest || (updates.manifest_version = 1), "supported_services" in manifest || (updates.supported_services = ["tracker"]), Object.keys(updates).length > 0 && await manifestManager.update(updates);
2117
+ }
2118
+
2172
2119
  // src/commands/up/utils/updatePackageDependencies.ts
2173
- var import_fs11 = require("fs"), import_promises6 = require("fs/promises");
2120
+ var import_fs12 = require("fs"), import_promises5 = require("fs/promises");
2174
2121
  var updatePackageDependencies = async (dependencies) => {
2175
2122
  let packageJsonPath = "./package.json", templatePackageJsonPath = getTemplatePath("package.json");
2176
- if (!(0, import_fs11.existsSync)(packageJsonPath) || !(0, import_fs11.existsSync)(templatePackageJsonPath))
2123
+ if (!(0, import_fs12.existsSync)(packageJsonPath) || !(0, import_fs12.existsSync)(templatePackageJsonPath))
2177
2124
  return;
2178
- let packageJsonContent = await (0, import_promises6.readFile)(packageJsonPath, "utf-8"), templatePackageJsonContent = await (0, import_promises6.readFile)(templatePackageJsonPath, "utf-8"), packageJson = JSON.parse(packageJsonContent), templatePackageJson = JSON.parse(templatePackageJsonContent), updated = !1;
2125
+ let packageJsonContent = await (0, import_promises5.readFile)(packageJsonPath, "utf-8"), templatePackageJsonContent = await (0, import_promises5.readFile)(templatePackageJsonPath, "utf-8"), packageJson = JSON.parse(packageJsonContent), templatePackageJson = JSON.parse(templatePackageJsonContent), updated = !1;
2179
2126
  dependencies.forEach((dep) => {
2180
2127
  packageJson.dependencies?.[dep] && templatePackageJson.dependencies?.[dep] && packageJson.dependencies[dep] !== templatePackageJson.dependencies[dep] && (packageJson.dependencies[dep] = templatePackageJson.dependencies[dep], updated = !0);
2181
- }), updated && await (0, import_promises6.writeFile)(packageJsonPath, JSON.stringify(packageJson, null, 4) + `
2128
+ }), updated && await (0, import_promises5.writeFile)(packageJsonPath, JSON.stringify(packageJson, null, 4) + `
2182
2129
  `, "utf-8");
2183
2130
  };
2184
2131
 
@@ -2188,40 +2135,72 @@ var templateFiles = ["manifest.schema.json", "vite.config.ts"], templateDeps = [
2188
2135
  "@yandex-data-ui/tracker-pub-api-types"
2189
2136
  ];
2190
2137
  async function up() {
2191
- await copyTemplateFiles(templateFiles), await updatePackageDependencies(templateDeps);
2138
+ await copyTemplateFiles(templateFiles), await updateManifestCompatibility(), await updatePackageDependencies(templateDeps);
2192
2139
  }
2193
2140
 
2194
2141
  // src/utils/buildArchive.ts
2195
- var import_path11 = __toESM(require("path")), import_archiver = __toESM(require("archiver"));
2142
+ var import_path12 = __toESM(require("path")), import_archiver = __toESM(require("archiver"));
2196
2143
  init_logger();
2197
2144
 
2198
2145
  // src/utils/marketplace.ts
2199
- var import_fs12 = __toESM(require("fs")), import_path10 = __toESM(require("path"));
2146
+ var import_fs13 = __toESM(require("fs")), import_path11 = __toESM(require("path"));
2200
2147
  init_logger();
2201
2148
  var MARKETPLACE_DIR = "marketplace", MARKETPLACE_INDEX_FILE = "index.md", MARKETPLACE_HEADER_IMAGE_FILE = "header-image.jpg", PUBLIC_DIR = "public", PUBLIC_LOGO_FILE = "logo.svg", MANIFEST_FILE = "manifest.json", DIST_DIR = "dist", SRC_DIR = "src", ensurePathExists = (targetPath, errorMessage) => {
2202
- import_fs12.default.existsSync(targetPath) || (logger_default.error(errorMessage), process.exit(1));
2149
+ import_fs13.default.existsSync(targetPath) || (logger_default.error(errorMessage), process.exit(1));
2150
+ }, ensureManifestCatalogFields = (manifest) => {
2151
+ (!manifest.name?.ru || !manifest.name?.en) && (logger_default.error(
2152
+ 'Manifest field "name" is required for publishing. Add localized "name.ru" and "name.en" to ./manifest.json.'
2153
+ ), process.exit(1)), (!manifest.description?.ru || !manifest.description?.en) && (logger_default.error(
2154
+ 'Manifest field "description" is required for publishing. Add localized "description.ru" and "description.en" to ./manifest.json.'
2155
+ ), process.exit(1));
2203
2156
  };
2204
2157
  function validateMarketplaceAssets() {
2205
- let marketplaceDir = import_path10.default.resolve(MARKETPLACE_DIR);
2158
+ let marketplaceDir = import_path11.default.resolve(MARKETPLACE_DIR);
2206
2159
  ensurePathExists(
2207
2160
  marketplaceDir,
2208
2161
  "Marketplace folder not found. Create ./marketplace and add index.md and header-image.jpg before publishing."
2209
2162
  ), ensurePathExists(
2210
- import_path10.default.join(marketplaceDir, MARKETPLACE_INDEX_FILE),
2163
+ import_path11.default.join(marketplaceDir, MARKETPLACE_INDEX_FILE),
2211
2164
  "Marketplace description not found. Add ./marketplace/index.md before publishing."
2212
2165
  ), ensurePathExists(
2213
- import_path10.default.join(marketplaceDir, MARKETPLACE_HEADER_IMAGE_FILE),
2166
+ import_path11.default.join(marketplaceDir, MARKETPLACE_HEADER_IMAGE_FILE),
2214
2167
  "Marketplace header image not found. Add ./marketplace/header-image.jpg before publishing."
2215
2168
  ), ensurePathExists(
2216
- import_path10.default.resolve(PUBLIC_DIR, PUBLIC_LOGO_FILE),
2169
+ import_path11.default.resolve(PUBLIC_DIR, PUBLIC_LOGO_FILE),
2217
2170
  "Plugin logo not found. Add ./public/logo.svg before publishing."
2218
2171
  );
2219
2172
  }
2220
2173
  function validateArchiveSources() {
2221
- ensurePathExists(import_path10.default.resolve(DIST_DIR), "Dist folder not found. Build the project first."), ensurePathExists(
2222
- import_path10.default.resolve(SRC_DIR),
2174
+ ensurePathExists(import_path11.default.resolve(DIST_DIR), "Dist folder not found. Build the project first."), ensurePathExists(
2175
+ import_path11.default.resolve(SRC_DIR),
2223
2176
  "Source folder not found. Add ./src before publishing."
2224
- ), ensurePathExists(import_path10.default.resolve(MANIFEST_FILE), "manifest.json not found in plugin root."), validateMarketplaceAssets();
2177
+ ), ensurePathExists(import_path11.default.resolve(MANIFEST_FILE), "manifest.json not found in plugin root."), validateMarketplaceAssets();
2178
+ }
2179
+ async function getMarketplaceCatalogEntry() {
2180
+ let indexPath = import_path11.default.resolve(MARKETPLACE_DIR, MARKETPLACE_INDEX_FILE);
2181
+ ensurePathExists(
2182
+ indexPath,
2183
+ "Marketplace description not found. Add ./marketplace/index.md before uploading."
2184
+ );
2185
+ let manifest = await manifestManager.load();
2186
+ ensureManifestCatalogFields(manifest);
2187
+ let longDescription = import_fs13.default.readFileSync(indexPath, "utf-8").trim();
2188
+ return {
2189
+ name: manifest.name.ru,
2190
+ description: manifest.description.ru,
2191
+ longDescription,
2192
+ tags: manifest.keywords ?? []
2193
+ };
2194
+ }
2195
+ function getMarketplaceHeaderImage() {
2196
+ let imagePath = import_path11.default.resolve(MARKETPLACE_DIR, MARKETPLACE_HEADER_IMAGE_FILE);
2197
+ return ensurePathExists(
2198
+ imagePath,
2199
+ "Marketplace header image not found. Add ./marketplace/header-image.jpg before uploading."
2200
+ ), {
2201
+ buffer: import_fs13.default.readFileSync(imagePath),
2202
+ filename: MARKETPLACE_HEADER_IMAGE_FILE
2203
+ };
2225
2204
  }
2226
2205
  var marketplacePaths = {
2227
2206
  distDir: DIST_DIR,
@@ -2231,41 +2210,55 @@ var marketplacePaths = {
2231
2210
  };
2232
2211
 
2233
2212
  // src/utils/buildArchive.ts
2213
+ init_verbose();
2234
2214
  async function buildArchive() {
2235
- return validateArchiveSources(), new Promise((resolve2, reject) => {
2215
+ return validateArchiveSources(), (await manifestManager.load()).manifest_version === void 0 && (logger_default.error(
2216
+ 'manifest_version is missing in manifest.json. Run "tracker-cli up" or add "manifest_version": 1 manually before uploading.'
2217
+ ), process.exit(1)), new Promise((resolve2, reject) => {
2236
2218
  let archive = (0, import_archiver.default)("zip", { zlib: { level: 9 } }), chunks = [];
2237
2219
  archive.on("data", (chunk) => chunks.push(chunk)), archive.on("end", () => {
2238
2220
  let buffer = Buffer.concat(chunks);
2239
- logger_default.info(`\u{1F4E6} Archive created (${buffer.length} bytes)`), resolve2(buffer);
2240
- }), archive.on("error", reject), archive.file(import_path11.default.resolve(marketplacePaths.manifestFile), { name: "manifest.json" }), archive.directory(import_path11.default.resolve(marketplacePaths.distDir), "dist"), archive.directory(import_path11.default.resolve(marketplacePaths.srcDir), "src"), archive.directory(import_path11.default.resolve(marketplacePaths.marketplaceDir), "marketplace"), archive.finalize();
2221
+ if (logger_default.info(`\u{1F4E6} Archive created (${buffer.length} bytes)`), isVerboseEnabled()) {
2222
+ let debugPath = writeVerboseArtifact("archive/latest-upload.zip", buffer);
2223
+ logger_default.info(`[platform-api-debug] Saved archive copy to ${debugPath}`);
2224
+ }
2225
+ resolve2(buffer);
2226
+ }), archive.on("error", reject), archive.file(import_path12.default.resolve(marketplacePaths.manifestFile), { name: "manifest.json" }), archive.glob(
2227
+ "**/*",
2228
+ {
2229
+ cwd: import_path12.default.resolve(marketplacePaths.distDir),
2230
+ ignore: ["**/.DS_Store"]
2231
+ },
2232
+ { prefix: "dist/" }
2233
+ ), archive.glob(
2234
+ "**/*",
2235
+ {
2236
+ cwd: import_path12.default.resolve(marketplacePaths.srcDir),
2237
+ ignore: ["**/.DS_Store"]
2238
+ },
2239
+ { prefix: "src/" }
2240
+ ), archive.finalize();
2241
2241
  });
2242
2242
  }
2243
2243
 
2244
2244
  // src/commands/upload/index.ts
2245
2245
  init_logger();
2246
- async function upload() {
2247
- let manifest = await manifestManager.load(), state = await pluginStateManager.load();
2248
- if (!state.trackerIssueKey) {
2249
- logger_default.warning('Plugin is not registered yet. Run "tracker-cli register" first.'), logger_default.suggest("tracker-cli register");
2250
- return;
2251
- }
2252
- logger_default.info("\u{1F528} Building plugin..."), await build(), logger_default.info("\u{1F4E6} Creating archive...");
2253
- let archiveBuffer = await buildArchive(), archiveName = `${manifest.slug}-${manifest.version}.zip`;
2254
- logger_default.info(`\u2B06\uFE0F Attaching ${archiveName} to ${state.trackerIssueKey}...`), await attachFileToIssue(state.trackerIssueKey, archiveBuffer, archiveName), await addIssueComment(
2255
- state.trackerIssueKey,
2256
- `Updated bundle: \`${archiveName}\` (${archiveBuffer.length} bytes)`
2257
- ), logger_default.success(`Bundle updated on ${state.trackerIssueKey}: ${archiveName}`);
2258
- }
2259
2246
 
2260
2247
  // src/api/platform.ts
2261
2248
  var import_form_data2 = __toESM(require("form-data"));
2262
2249
 
2263
2250
  // src/api/platformApiRequest.ts
2264
2251
  var import_node_fetch3 = __toESM(require("node-fetch"));
2252
+ init_logger();
2253
+ init_verbose();
2254
+ var PLATFORM_API_TIMEOUT_MS = Number(process.env.TRACKER_CLI_PLATFORM_TIMEOUT_MS || 3e4);
2265
2255
  function buildRequestHeaders(headers, formData) {
2266
- let authHeader = authManager.authHeader, requestHeaders = {
2256
+ let authHeader = authManager.authHeader;
2257
+ if (!authHeader)
2258
+ throw new Error('Not authenticated. Run "tracker-cli login" first.');
2259
+ let requestHeaders = {
2267
2260
  Accept: "*/*",
2268
- ...authHeader ? { Authorization: authHeader } : {},
2261
+ Authorization: authHeader,
2269
2262
  ...headers
2270
2263
  };
2271
2264
  return formData || (requestHeaders["Content-Type"] = "application/json"), requestHeaders;
@@ -2300,25 +2293,52 @@ async function platformApiRequest(endpoint, options = {}) {
2300
2293
  method,
2301
2294
  headers: { ...requestHeaders, ...additionalHeaders },
2302
2295
  body: requestBody
2303
- }, queryString = buildQueryString2(queryParams), url = `${await getPlatformApiUrl2()}${endpoint}${queryString}`, response = await (0, import_node_fetch3.default)(url, requestOptions);
2304
- if (!response.ok) {
2305
- let errorText = await response.text(), errorMessage = errorText;
2306
- try {
2307
- let errorJson = JSON.parse(errorText);
2308
- errorJson.message && (errorMessage = errorJson.message);
2309
- } catch {
2296
+ }, queryString = buildQueryString2(queryParams), url = `${getPlatformBaseUrl2()}${endpoint}${queryString}`, controller = new AbortController(), timeout = setTimeout(() => controller.abort(), PLATFORM_API_TIMEOUT_MS);
2297
+ if (isVerboseEnabled() && (logger_default.info(
2298
+ `[platform-api-debug] Request: method=${method} url=${url} hasBody=${!!body} hasFormData=${!!formData} timeoutMs=${PLATFORM_API_TIMEOUT_MS}`
2299
+ ), body !== void 0 && logger_default.info(`[platform-api-debug] Request body: ${JSON.stringify(body)}`), formData)) {
2300
+ let debugPath = writeVerboseArtifact(
2301
+ "requests/latest-form-data.txt",
2302
+ `method=${method}
2303
+ url=${url}
2304
+ headers=${JSON.stringify({ ...requestHeaders, ...additionalHeaders }, null, 2)}
2305
+ formData=present
2306
+ `
2307
+ );
2308
+ logger_default.info(`[platform-api-debug] Saved form-data request metadata to ${debugPath}`);
2309
+ }
2310
+ try {
2311
+ let response = await (0, import_node_fetch3.default)(url, {
2312
+ ...requestOptions,
2313
+ signal: controller.signal
2314
+ });
2315
+ if (isVerboseEnabled() && logger_default.info(
2316
+ `[platform-api-debug] Response: method=${method} url=${url} status=${response.status} contentType=${response.headers.get("content-type") ?? "n/a"}`
2317
+ ), !response.ok) {
2318
+ let errorText = await response.text(), errorMessage = errorText;
2319
+ try {
2320
+ let errorJson = JSON.parse(errorText);
2321
+ errorJson.message && (errorMessage = errorJson.message);
2322
+ } catch {
2323
+ }
2324
+ throw new Error(errorMessage);
2310
2325
  }
2311
- throw new Error(errorMessage);
2326
+ return parseResponse2(response);
2327
+ } catch (error2) {
2328
+ throw error2 instanceof Error && error2.name === "AbortError" ? new Error(
2329
+ `Platform API request timed out after ${PLATFORM_API_TIMEOUT_MS}ms: ${method} ${url}`
2330
+ ) : error2;
2331
+ } finally {
2332
+ clearTimeout(timeout);
2312
2333
  }
2313
- return parseResponse2(response);
2314
2334
  }
2315
2335
 
2316
2336
  // src/api/platform.ts
2317
- async function createPlugin(manifestContent) {
2337
+ async function createPlugin(archiveBuffer, archiveName) {
2318
2338
  let formData = new import_form_data2.default();
2319
- return formData.append("archive", manifestContent, {
2320
- filename: "manifest.json",
2321
- contentType: "application/json"
2339
+ return formData.append("archive", archiveBuffer, {
2340
+ filename: archiveName,
2341
+ contentType: "application/zip"
2322
2342
  }), platformApiRequest("/api/v1/plugins", {
2323
2343
  method: "POST",
2324
2344
  formData
@@ -2340,6 +2360,16 @@ async function updateCatalogEntry(pluginId, catalogData) {
2340
2360
  body: catalogData
2341
2361
  });
2342
2362
  }
2363
+ async function updateCatalogImage(pluginId, imageBuffer, imageName) {
2364
+ let formData = new import_form_data2.default();
2365
+ return formData.append("image", imageBuffer, {
2366
+ filename: imageName,
2367
+ contentType: "image/jpeg"
2368
+ }), platformApiRequest(`/api/v1/plugins/${pluginId}/catalog-image`, {
2369
+ method: "PUT",
2370
+ formData
2371
+ });
2372
+ }
2343
2373
  async function submitPlugin(pluginId, version) {
2344
2374
  return platformApiRequest(`/api/v1/plugins/${pluginId}/submit`, {
2345
2375
  method: "POST",
@@ -2358,11 +2388,6 @@ async function getPluginVersions(pluginId) {
2358
2388
  method: "GET"
2359
2389
  });
2360
2390
  }
2361
- async function getMyPlugins() {
2362
- return platformApiRequest("/api/v1/plugins/my", {
2363
- method: "GET"
2364
- });
2365
- }
2366
2391
  async function checkSlugAvailability(slug) {
2367
2392
  return platformApiRequest(`/api/v1/slugs/${slug}/available`, {
2368
2393
  method: "GET"
@@ -2415,19 +2440,6 @@ async function whoami() {
2415
2440
  }
2416
2441
  }
2417
2442
 
2418
- // src/commands/catalog/platform.ts
2419
- init_logger();
2420
- async function catalogPlatform() {
2421
- let manifest = await manifestManager.load(), pluginId = await promptPluginId(manifest);
2422
- logger_default.newLine(), logger_default.info("\u{1F4DD} Catalog entry information:");
2423
- let name = await promptPluginName(), description = await promptPluginDescription(), tags = await promptPluginTags();
2424
- logger_default.newLine(), logger_default.info("\u{1F4E4} Updating catalog entry..."), await updateCatalogEntry(pluginId, {
2425
- name,
2426
- description,
2427
- tags
2428
- }), logger_default.success("Catalog entry updated successfully!");
2429
- }
2430
-
2431
2443
  // src/commands/info/platform.ts
2432
2444
  init_logger();
2433
2445
  async function infoPlatform(pluginId) {
@@ -2441,36 +2453,6 @@ async function infoPlatform(pluginId) {
2441
2453
  }
2442
2454
  }
2443
2455
 
2444
- // src/commands/list/platform.ts
2445
- init_logger();
2446
- async function listPlatform() {
2447
- logger_default.info("\u23F3 Fetching my plugins...");
2448
- try {
2449
- let plugins = await getMyPlugins();
2450
- showPluginList(plugins);
2451
- } catch (error2) {
2452
- throw logger_default.error("Failed to fetch plugins list"), error2;
2453
- }
2454
- }
2455
-
2456
- // src/commands/register/platform.ts
2457
- init_logger();
2458
- async function registerPlatform() {
2459
- let manifest = await manifestManager.load();
2460
- if (manifest.id)
2461
- try {
2462
- let pluginInfo = await getPluginInfo(manifest.id);
2463
- logger_default.error(`Plugin is already registered with ID: ${pluginInfo.id}`), process.exit(1);
2464
- } catch {
2465
- }
2466
- await build(), logger_default.info("\u23F3 Creating plugin...");
2467
- let manifestContent = await manifestManager.loadRaw(), response = await createPlugin(manifestContent);
2468
- showPluginCreated(response), await manifestManager.update({ id: response.id }), logger_default.commands("\u{1F4A1} Next steps", [
2469
- "tracker-cli catalog # Update catalog data",
2470
- "tracker-cli submit # Submit for review"
2471
- ]);
2472
- }
2473
-
2474
2456
  // src/commands/submit/platform.ts
2475
2457
  init_logger();
2476
2458
 
@@ -2502,44 +2484,115 @@ async function validateAndGetUniqueSlug(currentSlug) {
2502
2484
  }
2503
2485
 
2504
2486
  // src/commands/submit/platform.ts
2487
+ var SUBMIT_TRANSITION_DISPLAY = "waiting for approve";
2505
2488
  async function submitPlatform() {
2506
- let manifest = await manifestManager.load(), pluginId = manifest.id;
2507
- if (!pluginId) {
2508
- logger_default.warning("Please register the plugin first"), logger_default.suggest("tracker-cli register");
2509
- return;
2489
+ let manifest = await manifestManager.load(), state = await pluginStateManager.load();
2490
+ validateMarketplaceAssets(), await build(), logger_default.info("\u{1F4E6} Creating archive...");
2491
+ let archiveBuffer = await buildArchive(), archiveName = `${manifest.slug}-${manifest.version}.zip`, pluginId = manifest.id;
2492
+ if (pluginId)
2493
+ logger_default.info("\u{1F4E4} Updating plugin archive..."), await updatePluginArchive(pluginId, archiveBuffer, archiveName);
2494
+ else {
2495
+ logger_default.info("\u23F3 Creating plugin...");
2496
+ let createdPlugin = await createPlugin(archiveBuffer, archiveName);
2497
+ showPluginCreated(createdPlugin), pluginId = createdPlugin.id, await manifestManager.update({ id: pluginId });
2498
+ }
2499
+ let manifestContent = await manifestManager.loadRaw(), issueKey = state.trackerIssueKey;
2500
+ if (!issueKey) {
2501
+ logger_default.info("\u23F3 Creating plugin issue in Tracker...");
2502
+ let issue = await createPluginIssue({
2503
+ pluginId,
2504
+ slug: manifest.slug,
2505
+ version: manifest.version,
2506
+ manifestJson: manifestContent
2507
+ });
2508
+ await pluginStateManager.update({ trackerIssueKey: issue.key }), issueKey = issue.key;
2510
2509
  }
2510
+ let catalogEntry = await getMarketplaceCatalogEntry();
2511
+ logger_default.info("\u{1F4DD} Updating catalog entry from ./marketplace/index.md..."), await updateCatalogEntry(pluginId, catalogEntry);
2512
+ let headerImage = getMarketplaceHeaderImage();
2513
+ logger_default.info("\u{1F5BC}\uFE0F Uploading catalog image from ./marketplace/header-image.jpg..."), await updateCatalogImage(pluginId, headerImage.buffer, headerImage.filename);
2511
2514
  let validatedSlug = await validateAndGetUniqueSlug(manifest.slug);
2512
- validatedSlug !== manifest.slug && await manifestManager.update({ slug: validatedSlug });
2513
- let response = await submitPlugin(pluginId, manifest.version);
2514
- showPluginSubmitted(response), logger_default.newLine();
2515
+ validatedSlug !== manifest.slug && await manifestManager.update({ slug: validatedSlug }), await submitPlugin(pluginId, manifest.version);
2516
+ let transitions = await getIssueTransitions(issueKey), submitTransition = transitions.find(
2517
+ (t) => t.display?.toLowerCase().includes(SUBMIT_TRANSITION_DISPLAY) || t.id?.toLowerCase().includes("waitingforapprove") || t.id?.toLowerCase().includes("waiting")
2518
+ );
2519
+ if (!submitTransition) {
2520
+ let available = transitions.map((t) => `"${t.display ?? t.id}"`).join(", ");
2521
+ logger_default.error(
2522
+ `Could not find a "Waiting for approve" transition on ${issueKey}. Available: ${available}`
2523
+ );
2524
+ return;
2525
+ }
2526
+ logger_default.info("\u{1F680} Submitting for review..."), await executeIssueTransition(issueKey, submitTransition.id), await addIssueComment(issueKey, `Submitted version \`${manifest.version}\` for review.`), logger_default.success("Plugin submitted for review!"), logger_default.info(` Tracker issue : ${issueKey}`), logger_default.info(` Version : ${manifest.version}`), logger_default.newLine();
2515
2527
  }
2516
2528
 
2517
- // src/commands/upload/platform.ts
2529
+ // src/commands/submit/tracker.ts
2530
+ var import_crypto = require("crypto");
2518
2531
  init_logger();
2519
- async function uploadPlatform() {
2520
- let manifest = await manifestManager.load(), pluginId = manifest.id;
2521
- if (!pluginId) {
2522
- logger_default.warning("Please register the plugin first"), logger_default.suggest("tracker-cli register");
2523
- return;
2532
+ var SUBMIT_TRANSITION_DISPLAY2 = "waiting for approve";
2533
+ async function submitTracker() {
2534
+ let manifest = await manifestManager.load(), issueKey = (await pluginStateManager.load()).trackerIssueKey;
2535
+ if (issueKey)
2536
+ try {
2537
+ await getIssue(issueKey);
2538
+ } catch {
2539
+ issueKey = void 0;
2540
+ }
2541
+ let pluginId = manifest.id ?? (0, import_crypto.randomUUID)();
2542
+ manifest.id || await manifestManager.update({ id: pluginId });
2543
+ let manifestJson = await manifestManager.loadRaw();
2544
+ if (issueKey) {
2545
+ logger_default.verbose(`\u23F3 Updating issue ${issueKey} with latest manifest...`);
2546
+ let issueDescription = buildIssueDescription({
2547
+ pluginId,
2548
+ slug: manifest.slug,
2549
+ version: manifest.version,
2550
+ manifestJson
2551
+ });
2552
+ await updatePluginIssue(issueKey, { description: issueDescription });
2553
+ } else {
2554
+ logger_default.info("\u23F3 Creating plugin issue in Tracker...");
2555
+ let issue = await createPluginIssue({
2556
+ pluginId,
2557
+ slug: manifest.slug,
2558
+ version: manifest.version,
2559
+ manifestJson
2560
+ });
2561
+ await pluginStateManager.update({ trackerIssueKey: issue.key }), issueKey = issue.key, logger_default.info(` Tracker issue : ${issueKey}`);
2524
2562
  }
2525
- await build(), logger_default.info("\u{1F4E6} Creating archive...");
2563
+ logger_default.info("\u{1F528} Building plugin..."), await build(), logger_default.info("\u{1F4E6} Creating archive...");
2526
2564
  let archiveBuffer = await buildArchive(), archiveName = `${manifest.slug}-${manifest.version}.zip`;
2527
- logger_default.info("\u{1F4E4} Updating plugin archive..."), await updatePluginArchive(pluginId, archiveBuffer, archiveName), logger_default.success("Plugin archive updated successfully!");
2565
+ logger_default.verbose(`\u2B06\uFE0F Attaching ${archiveName} to ${issueKey}...`), await attachFileToIssue(issueKey, archiveBuffer, archiveName), await addIssueComment(
2566
+ issueKey,
2567
+ `Updated bundle: \`${archiveName}\` (${archiveBuffer.length} bytes)`
2568
+ ), logger_default.verbose(`\u23F3 Fetching available transitions for ${issueKey}...`);
2569
+ let transitions = await getIssueTransitions(issueKey), submitTransition = transitions.find(
2570
+ (t) => t.display?.toLowerCase().includes(SUBMIT_TRANSITION_DISPLAY2) || t.id?.toLowerCase().includes("waitingforapprove") || t.id?.toLowerCase().includes("waiting")
2571
+ );
2572
+ if (!submitTransition) {
2573
+ let available = transitions.map((t) => `"${t.display ?? t.id}"`).join(", ");
2574
+ logger_default.error(
2575
+ `Could not find a "Waiting for approve" transition on ${issueKey}. Available: ${available}`
2576
+ );
2577
+ return;
2578
+ }
2579
+ logger_default.info("\u{1F680} Submitting plugin for review..."), await executeIssueTransition(issueKey, submitTransition.id), await addIssueComment(issueKey, `Submitted version \`${manifest.version}\` for review.`), logger_default.success("Plugin submitted for review!"), logger_default.info(` Tracker issue : ${issueKey}`), logger_default.info(` Version : ${manifest.version}`), logger_default.newLine();
2528
2580
  }
2529
2581
 
2530
2582
  // src/utils/getVersion.ts
2531
- var import_fs13 = require("fs"), import_path12 = require("path"), UNKNOWN_VERSION = "unknown", getVersionFromPackageJson = () => {
2583
+ var import_fs14 = require("fs"), import_path13 = require("path"), UNKNOWN_VERSION = "unknown", getVersionFromPackageJson = () => {
2532
2584
  try {
2533
- let packagePath = (0, import_path12.resolve)(__dirname, "../../package.json");
2534
- return (0, import_fs13.existsSync)(packagePath) && JSON.parse((0, import_fs13.readFileSync)(packagePath, "utf-8")).version || null;
2585
+ let packagePath = (0, import_path13.resolve)(__dirname, "../../package.json");
2586
+ return (0, import_fs14.existsSync)(packagePath) && JSON.parse((0, import_fs14.readFileSync)(packagePath, "utf-8")).version || null;
2535
2587
  } catch {
2536
2588
  return null;
2537
2589
  }
2538
- }, getVersion = () => "0.1.0-dev";
2590
+ }, getVersion = () => "0.2.0-dev";
2539
2591
 
2540
2592
  // src/index.ts
2541
2593
  init_logger();
2542
- function withErrorHandling2(fn) {
2594
+ init_verbose();
2595
+ function withErrorHandling(fn) {
2543
2596
  return async (...args) => {
2544
2597
  try {
2545
2598
  await fn(...args);
@@ -2550,19 +2603,21 @@ function withErrorHandling2(fn) {
2550
2603
  }
2551
2604
  async function main() {
2552
2605
  let program = new import_commander.Command(), version = getVersion();
2553
- await checkUpdate(version), program.name(CLI_NAME2).description(CLI_DESCRIPTION2).version(version), CLI_HELP_TEXT2 && program.addHelpText("before", CLI_HELP_TEXT2), program.command("feedback").description("Let us know what you think about Yandex Tracker plugins").action(() => {
2606
+ await checkUpdate(version), program.name(CLI_NAME2).description(CLI_DESCRIPTION2).version(version), CLI_HELP_TEXT2 && program.addHelpText("before", CLI_HELP_TEXT2), program.hook("preAction", (thisCommand) => {
2607
+ setVerboseEnabled(!!thisCommand.opts().verbose);
2608
+ }), registerDistributionCommands2(program), program.command("feedback").description("Let us know what you think about Yandex Tracker plugins").action(() => {
2554
2609
  logger_default.info(
2555
2610
  `Help us make Yandex Tracker even better.
2556
2611
  To report bugs or issues, go to: https://forms.yandex.ru/surveys/6767/`
2557
2612
  );
2558
- }), program.command("create").description("create your app").action(withErrorHandling2(create)), program.command("up").description("update plugin from old template to latest version").action(withErrorHandling2(up)), program.command("build").description("build your app").action(withErrorHandling2(build)), program.command("debug").description(
2613
+ }), program.command("create").description("create your app").action(withErrorHandling(create)), program.command("up").description("update plugin from old template to latest version").action(withErrorHandling(up)), program.command("build").description("build your app").action(withErrorHandling(build)), program.command("debug").description(
2559
2614
  "start a tunnel to connect your local code with the app running in the development environment"
2560
- ).option("--no-lint", "skip lint validation").action((options) => withErrorHandling2(debug)(options)), program.command("lint").description("run TypeScript and ESLint checks").action(withErrorHandling2(lint)), program.command("login").description("authenticate with Yandex Tracker").action(withErrorHandling2(login)), program.command("logout").description("remove authentication credentials").action(withErrorHandling2(logout)), program.command("whoami").description("display current authentication status").action(withErrorHandling2(async () => await whoami())), process.env.USE_BACKEND_TO_PUBLISH ? (program.command("register").description("register a new plugin and upload archive").action(withErrorHandling2(registerPlatform)), program.command("upload").description("upload plugin archive").action(withErrorHandling2(uploadPlatform)), program.command("update-catalog").description("update plugin catalog entry").action(withErrorHandling2(catalogPlatform)), program.command("submit").description("submit plugin for review").action(withErrorHandling2(submitPlatform)), program.command("list").description("list my plugins").action(withErrorHandling2(listPlatform)), program.command("info [plugin-id]").description(
2615
+ ).option("--no-lint", "skip lint validation").action((options) => withErrorHandling(debug)(options)), program.command("lint").description("run TypeScript and ESLint checks").action(withErrorHandling(lint)), program.command("login").description("authenticate with Yandex Tracker").action(withErrorHandling(login)), program.command("logout").description("remove authentication credentials").action(withErrorHandling(logout)), program.command("whoami").description("display current authentication status").action(withErrorHandling(async () => await whoami())), process.env.USE_TRACKER_TO_PUBLISH ? (program.command("submit").description("build, attach archive to Tracker issue, and submit for review").action(withErrorHandling(submitTracker)), program.command("list").description("list my plugins").action(withErrorHandling(list)), program.command("info [issue-key]").description(
2616
+ "get detailed information about a plugin (uses manifest.trackerIssueKey if issue-key not provided)"
2617
+ ).action(withErrorHandling(info2))) : (program.command("submit").description("create plugin, upload data, and submit for review").action(withErrorHandling(submitPlatform)), program.command("list").description("list my plugins").action(withErrorHandling(list)), program.command("info [plugin-id]").description(
2561
2618
  "get detailed information about a plugin (uses manifest.id if plugin-id not provided)"
2562
- ).action(withErrorHandling2(infoPlatform)), program.command("versions [plugin-id]").description(
2619
+ ).action(withErrorHandling(infoPlatform)), program.command("versions [plugin-id]").description(
2563
2620
  "list all versions of a plugin (uses manifest.id if plugin-id not provided)"
2564
- ).action(withErrorHandling2(versions))) : (program.command("register").description("register a new plugin and create a Tracker issue").action(withErrorHandling2(register)), program.command("upload").description("build and upload plugin bundle").action(withErrorHandling2(upload)), program.command("catalog").description("update plugin catalog metadata").action(withErrorHandling2(catalog)), program.command("submit").description("submit plugin for review").action(withErrorHandling2(submit)), program.command("list").description("list my plugins").action(withErrorHandling2(list)), program.command("info [issue-key]").description(
2565
- "get detailed information about a plugin (uses manifest.trackerIssueKey if issue-key not provided)"
2566
- ).action(withErrorHandling2(info2))), process.env.TRACKER_CLI_ADMIN && (program.command("deploy").description("build and upload plugin bundle to S3 for production").action(withErrorHandling2(deploy)), program.command("install").description("manage app installations").action(withErrorHandling2(install)), program.command("uninstall").description("uninstall app from all slots").action(withErrorHandling2(uninstall))), registerDistributionCommands2(program), await program.parseAsync(), process.exit(0);
2621
+ ).action(withErrorHandling(versions))), process.env.TRACKER_CLI_ADMIN && (program.command("deploy").description("build and upload plugin bundle to S3 for production").action(withErrorHandling(deploy)), program.command("install").description("manage app installations").action(withErrorHandling(install)), program.command("uninstall").description("uninstall app from all slots").action(withErrorHandling(uninstall))), await program.parseAsync(), process.exit(0);
2567
2622
  }
2568
2623
  main();