specweave 0.23.16 ā 0.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +93 -38
- package/CLAUDE.md +159 -11
- package/dist/plugins/specweave-github/lib/github-spec-content-sync.d.ts.map +1 -1
- package/dist/plugins/specweave-github/lib/github-spec-content-sync.js +57 -0
- package/dist/plugins/specweave-github/lib/github-spec-content-sync.js.map +1 -1
- package/dist/src/cli/commands/sync-spec-content.js +3 -0
- package/dist/src/cli/commands/sync-spec-content.js.map +1 -1
- package/dist/src/cli/helpers/ado-area-path-mapper.d.ts +89 -0
- package/dist/src/cli/helpers/ado-area-path-mapper.d.ts.map +1 -0
- package/dist/src/cli/helpers/ado-area-path-mapper.js +213 -0
- package/dist/src/cli/helpers/ado-area-path-mapper.js.map +1 -0
- package/dist/src/cli/helpers/issue-tracker/ado-auto-discover.d.ts +29 -0
- package/dist/src/cli/helpers/issue-tracker/ado-auto-discover.d.ts.map +1 -0
- package/dist/src/cli/helpers/issue-tracker/ado-auto-discover.js +109 -0
- package/dist/src/cli/helpers/issue-tracker/ado-auto-discover.js.map +1 -0
- package/dist/src/cli/helpers/issue-tracker/ado.d.ts +1 -0
- package/dist/src/cli/helpers/issue-tracker/ado.d.ts.map +1 -1
- package/dist/src/cli/helpers/issue-tracker/ado.js +2 -0
- package/dist/src/cli/helpers/issue-tracker/ado.js.map +1 -1
- package/dist/src/cli/helpers/smart-filter.d.ts +83 -0
- package/dist/src/cli/helpers/smart-filter.d.ts.map +1 -0
- package/dist/src/cli/helpers/smart-filter.js +265 -0
- package/dist/src/cli/helpers/smart-filter.js.map +1 -0
- package/dist/src/core/progress/progress-tracker.d.ts +4 -1
- package/dist/src/core/progress/progress-tracker.d.ts.map +1 -1
- package/dist/src/core/progress/progress-tracker.js +33 -4
- package/dist/src/core/progress/progress-tracker.js.map +1 -1
- package/dist/src/core/qa/quality-gate-decider.d.ts +1 -1
- package/dist/src/core/qa/quality-gate-decider.js +2 -2
- package/dist/src/core/qa/quality-gate-decider.js.map +1 -1
- package/dist/src/core/qa/risk-calculator.d.ts +2 -2
- package/dist/src/core/qa/risk-calculator.js +2 -2
- package/dist/src/core/spec-content-sync.d.ts +1 -1
- package/dist/src/core/spec-content-sync.d.ts.map +1 -1
- package/dist/src/core/validators/ac-presence-validator.d.ts +56 -0
- package/dist/src/core/validators/ac-presence-validator.d.ts.map +1 -0
- package/dist/src/core/validators/ac-presence-validator.js +149 -0
- package/dist/src/core/validators/ac-presence-validator.js.map +1 -0
- package/dist/src/integrations/ado/ado-dependency-loader.d.ts +1 -1
- package/dist/src/integrations/ado/ado-dependency-loader.d.ts.map +1 -1
- package/dist/src/integrations/ado/ado-dependency-loader.js +39 -7
- package/dist/src/integrations/ado/ado-dependency-loader.js.map +1 -1
- package/dist/src/integrations/ado/area-path-mapper.d.ts +137 -0
- package/dist/src/integrations/ado/area-path-mapper.d.ts.map +1 -0
- package/dist/src/integrations/ado/area-path-mapper.js +267 -0
- package/dist/src/integrations/ado/area-path-mapper.js.map +1 -0
- package/dist/src/integrations/jira/filter-processor.d.ts +126 -0
- package/dist/src/integrations/jira/filter-processor.d.ts.map +1 -0
- package/dist/src/integrations/jira/filter-processor.js +207 -0
- package/dist/src/integrations/jira/filter-processor.js.map +1 -0
- package/dist/src/integrations/jira/jira-client.d.ts +13 -0
- package/dist/src/integrations/jira/jira-client.d.ts.map +1 -1
- package/dist/src/integrations/jira/jira-client.js +33 -0
- package/dist/src/integrations/jira/jira-client.js.map +1 -1
- package/dist/src/utils/ac-embedder.d.ts +63 -0
- package/dist/src/utils/ac-embedder.d.ts.map +1 -0
- package/dist/src/utils/ac-embedder.js +217 -0
- package/dist/src/utils/ac-embedder.js.map +1 -0
- package/dist/src/utils/env-manager.d.ts +86 -0
- package/dist/src/utils/env-manager.d.ts.map +1 -0
- package/dist/src/utils/env-manager.js +188 -0
- package/dist/src/utils/env-manager.js.map +1 -0
- package/package.json +1 -1
- package/plugins/specweave/.claude-plugin/plugin.json +1 -1
- package/plugins/specweave/agents/AGENTS-INDEX.md +1 -1
- package/plugins/specweave/agents/increment-quality-judge-v2/AGENT.md +9 -9
- package/plugins/specweave/commands/specweave-do.md +37 -0
- package/plugins/specweave/commands/specweave-done.md +159 -0
- package/plugins/specweave/commands/specweave-embed-acs.md +446 -0
- package/plugins/specweave/commands/specweave-next.md +148 -3
- package/plugins/specweave/commands/specweave-qa.md +2 -2
- package/plugins/specweave/hooks/lib/migrate-increment-work.sh +1 -1
- package/plugins/specweave/hooks/lib/migrate-increment-work.sh.bak +245 -0
- package/plugins/specweave/hooks/lib/sync-spec-content.sh +2 -2
- package/plugins/specweave/hooks/lib/sync-spec-content.sh.bak +149 -0
- package/plugins/specweave/hooks/lib/update-status-line.sh +34 -4
- package/plugins/specweave/hooks/lib/validate-spec-status.sh +1 -1
- package/plugins/specweave/hooks/lib/validate-spec-status.sh.bak +163 -0
- package/plugins/specweave/hooks/post-first-increment.sh +1 -1
- package/plugins/specweave/hooks/post-first-increment.sh.bak +61 -0
- package/plugins/specweave/hooks/post-spec-update.sh +1 -1
- package/plugins/specweave/hooks/post-spec-update.sh.bak +158 -0
- package/plugins/specweave/hooks/post-user-story-complete.sh +1 -1
- package/plugins/specweave/hooks/post-user-story-complete.sh.bak +179 -0
- package/plugins/specweave/hooks/pre-command-deduplication.sh +1 -1
- package/plugins/specweave/hooks/pre-command-deduplication.sh.bak +83 -0
- package/plugins/specweave/hooks/pre-increment-start.sh +168 -0
- package/plugins/specweave/hooks/user-prompt-submit.sh +1 -1
- package/plugins/specweave/hooks/user-prompt-submit.sh.bak +386 -0
- package/plugins/specweave/skills/SKILLS-INDEX.md +1 -1
- package/plugins/specweave/skills/specweave-framework/SKILL.md +1 -1
- package/plugins/specweave-ado/.claude-plugin/plugin.json +1 -1
- package/plugins/specweave-ado/agents/ado-manager/AGENT.md +23 -0
- package/plugins/specweave-ado/agents/ado-multi-project-mapper/AGENT.md +23 -0
- package/plugins/specweave-ado/agents/ado-sync-judge/AGENT.md +23 -0
- package/plugins/specweave-ado/commands/specweave-ado-import-projects.md +331 -0
- package/plugins/specweave-alternatives/.claude-plugin/plugin.json +10 -0
- package/plugins/specweave-alternatives/commands/alternatives-analyze.md +336 -0
- package/plugins/specweave-alternatives/skills/architecture-alternatives/SKILL.md +651 -0
- package/plugins/specweave-alternatives/skills/bmad-method/SKILL.md +420 -0
- package/plugins/specweave-alternatives/skills/spec-kit-expert/SKILL.md +487 -0
- package/plugins/specweave-backend/agents/database-optimizer/AGENT.md +23 -0
- package/plugins/specweave-backend/commands/api-scaffold.md +80 -0
- package/plugins/specweave-backend/commands/crud-generate.md +109 -0
- package/plugins/specweave-backend/commands/migration-generate.md +139 -0
- package/plugins/specweave-confluent/agents/confluent-architect/AGENT.md +23 -0
- package/plugins/specweave-confluent/commands/connector-deploy.md +154 -0
- package/plugins/specweave-confluent/commands/ksqldb-query.md +179 -0
- package/plugins/specweave-confluent/commands/schema-register.md +123 -0
- package/plugins/specweave-core/.claude-plugin/plugin.json +21 -0
- package/plugins/specweave-core/commands/architecture-review.md +288 -0
- package/plugins/specweave-core/commands/code-review.md +213 -0
- package/plugins/specweave-core/commands/refactor-plan.md +249 -0
- package/plugins/specweave-core/skills/code-quality/SKILL.md +157 -0
- package/plugins/specweave-core/skills/design-patterns/SKILL.md +244 -0
- package/plugins/specweave-core/skills/software-architecture/SKILL.md +83 -0
- package/plugins/specweave-cost-optimizer/.claude-plugin/plugin.json +22 -0
- package/plugins/specweave-cost-optimizer/commands/cost-analyze.md +360 -0
- package/plugins/specweave-cost-optimizer/commands/cost-optimize.md +480 -0
- package/plugins/specweave-cost-optimizer/skills/aws-cost-expert/SKILL.md +416 -0
- package/plugins/specweave-cost-optimizer/skills/cloud-pricing/SKILL.md +325 -0
- package/plugins/specweave-cost-optimizer/skills/cost-optimization/SKILL.md +337 -0
- package/plugins/specweave-diagrams/.claude-plugin/plugin.json +1 -1
- package/plugins/specweave-diagrams/agents/diagrams-architect/AGENT.md +23 -0
- package/plugins/specweave-diagrams/commands/diagrams-generate.md +168 -0
- package/plugins/specweave-docs/.claude-plugin/plugin.json +10 -0
- package/plugins/specweave-docs/commands/docs-generate.md +441 -0
- package/plugins/specweave-docs/commands/docs-init.md +334 -0
- package/plugins/specweave-docs/skills/docusaurus/SKILL.md +581 -0
- package/plugins/specweave-docs/skills/spec-driven-brainstorming/SKILL.md +689 -0
- package/plugins/specweave-docs/skills/technical-writing/SKILL.md +1039 -0
- package/plugins/specweave-docs-preview/.claude-plugin/plugin.json +1 -1
- package/plugins/specweave-figma/.claude-plugin/plugin.json +23 -0
- package/plugins/specweave-figma/commands/figma-import.md +690 -0
- package/plugins/specweave-figma/commands/figma-to-react.md +834 -0
- package/plugins/specweave-figma/commands/figma-tokens.md +815 -0
- package/plugins/specweave-frontend/.claude-plugin/plugin.json +21 -0
- package/plugins/specweave-frontend/agents/frontend-architect/AGENT.md +387 -0
- package/plugins/specweave-frontend/agents/frontend-architect/README.md +385 -0
- package/plugins/specweave-frontend/agents/frontend-architect/examples.md +590 -0
- package/plugins/specweave-frontend/agents/frontend-architect/templates/component-template.tsx +152 -0
- package/plugins/specweave-frontend/agents/frontend-architect/templates/hook-template.ts +311 -0
- package/plugins/specweave-frontend/agents/frontend-architect/templates/page-template.tsx +228 -0
- package/plugins/specweave-frontend/commands/component-generate.md +510 -0
- package/plugins/specweave-frontend/commands/design-system-init.md +494 -0
- package/plugins/specweave-frontend/commands/frontend-scaffold.md +207 -0
- package/plugins/specweave-frontend/commands/nextjs-setup.md +396 -0
- package/plugins/specweave-frontend/skills/design-system-architect/SKILL.md +278 -0
- package/plugins/specweave-frontend/skills/frontend/SKILL.md +420 -0
- package/plugins/specweave-frontend/skills/nextjs/SKILL.md +546 -0
- package/plugins/specweave-github/.claude-plugin/plugin.json +1 -1
- package/plugins/specweave-github/agents/github-manager/AGENT.md +23 -0
- package/plugins/specweave-github/agents/github-task-splitter/AGENT.md +25 -0
- package/plugins/specweave-github/agents/user-story-updater/AGENT.md +25 -0
- package/plugins/specweave-github/hooks/.specweave/logs/hooks-debug.log +194 -0
- package/plugins/specweave-github/lib/github-spec-content-sync.js +49 -0
- package/plugins/specweave-github/lib/github-spec-content-sync.ts +67 -0
- package/plugins/specweave-infrastructure/.claude-plugin/plugin.json +1 -1
- package/plugins/specweave-infrastructure/agents/devops/AGENT.md +26 -0
- package/plugins/specweave-infrastructure/agents/network-engineer/AGENT.md +26 -0
- package/plugins/specweave-infrastructure/agents/observability-engineer/AGENT.md +26 -0
- package/plugins/specweave-infrastructure/agents/performance-engineer/AGENT.md +26 -0
- package/plugins/specweave-infrastructure/agents/sre/AGENT.md +26 -0
- package/plugins/specweave-jira/.claude-plugin/plugin.json +1 -1
- package/plugins/specweave-jira/agents/jira-manager/AGENT.md +26 -0
- package/plugins/specweave-jira/commands/import-projects.js +183 -0
- package/plugins/specweave-jira/commands/import-projects.md +97 -0
- package/plugins/specweave-jira/commands/import-projects.ts +288 -0
- package/plugins/specweave-jira/commands/specweave-jira-import-projects.md +298 -0
- package/plugins/specweave-kafka/.claude-plugin/plugin.json +1 -1
- package/plugins/specweave-kafka/agents/kafka-architect/AGENT.md +26 -0
- package/plugins/specweave-kafka/agents/kafka-devops/AGENT.md +26 -0
- package/plugins/specweave-kafka/agents/kafka-observability/AGENT.md +26 -0
- package/plugins/specweave-kafka-streams/.claude-plugin/plugin.json +1 -1
- package/plugins/specweave-kubernetes/agents/kubernetes-architect/AGENT.md +26 -0
- package/plugins/specweave-kubernetes/commands/cluster-setup.md +262 -0
- package/plugins/specweave-kubernetes/commands/deployment-generate.md +242 -0
- package/plugins/specweave-kubernetes/commands/helm-scaffold.md +333 -0
- package/plugins/specweave-ml/.claude-plugin/plugin.json +3 -3
- package/plugins/specweave-ml/agents/data-scientist/AGENT.md +26 -0
- package/plugins/specweave-ml/agents/ml-engineer/AGENT.md +26 -0
- package/plugins/specweave-ml/agents/mlops-engineer/AGENT.md +26 -0
- package/plugins/specweave-mobile/agents/mobile-architect/AGENT.md +26 -0
- package/plugins/specweave-mobile/commands/app-scaffold.md +233 -0
- package/plugins/specweave-mobile/commands/build-config.md +256 -0
- package/plugins/specweave-mobile/commands/screen-generate.md +289 -0
- package/plugins/specweave-n8n/.claude-plugin/plugin.json +1 -1
- package/plugins/specweave-payments/agents/payment-integration/AGENT.md +26 -0
- package/plugins/specweave-plugin-dev/.claude-plugin/plugin.json +20 -0
- package/plugins/specweave-plugin-dev/commands/plugin-create.md +333 -0
- package/plugins/specweave-plugin-dev/commands/plugin-publish.md +339 -0
- package/plugins/specweave-plugin-dev/commands/plugin-test.md +293 -0
- package/plugins/specweave-plugin-dev/skills/claude-sdk/SKILL.md +162 -0
- package/plugins/specweave-plugin-dev/skills/marketplace-publishing/SKILL.md +263 -0
- package/plugins/specweave-plugin-dev/skills/plugin-development/SKILL.md +316 -0
- package/plugins/specweave-release/.claude-plugin/plugin.json +1 -1
- package/plugins/specweave-release/agents/release-manager/AGENT.md +27 -0
- package/plugins/specweave-release/commands/specweave-release-npm.md +110 -0
- package/plugins/specweave-release/hooks/.specweave/logs/dora-tracking.log +168 -0
- package/plugins/specweave-testing/.claude-plugin/plugin.json +21 -0
- package/plugins/specweave-testing/agents/qa-engineer/AGENT.md +797 -0
- package/plugins/specweave-testing/agents/qa-engineer/README.md +443 -0
- package/plugins/specweave-testing/agents/qa-engineer/templates/playwright-e2e-test.ts +470 -0
- package/plugins/specweave-testing/agents/qa-engineer/templates/test-data-factory.ts +507 -0
- package/plugins/specweave-testing/agents/qa-engineer/templates/vitest-unit-test.ts +400 -0
- package/plugins/specweave-testing/agents/qa-engineer/test-strategies.md +726 -0
- package/plugins/specweave-testing/commands/e2e-setup.md +1081 -0
- package/plugins/specweave-testing/commands/test-coverage.md +979 -0
- package/plugins/specweave-testing/commands/test-generate.md +1156 -0
- package/plugins/specweave-testing/commands/test-init.md +409 -0
- package/plugins/specweave-testing/skills/e2e-playwright/SKILL.md +769 -0
- package/plugins/specweave-testing/skills/tdd-expert/SKILL.md +934 -0
- package/plugins/specweave-testing/skills/unit-testing-expert/SKILL.md +1011 -0
- package/plugins/specweave-tooling/.claude-plugin/plugin.json +22 -0
- package/plugins/specweave-tooling/commands/specweave-tooling-skill-create.md +691 -0
- package/plugins/specweave-tooling/commands/specweave-tooling-skill-package.md +751 -0
- package/plugins/specweave-tooling/commands/specweave-tooling-skill-validate.md +858 -0
- package/plugins/specweave-ui/.claude-plugin/plugin.json +10 -0
- package/plugins/specweave-ui/commands/ui-automate.md +199 -0
- package/plugins/specweave-ui/commands/ui-inspect.md +70 -0
- package/plugins/specweave-ui/skills/browser-automation/SKILL.md +314 -0
- package/plugins/specweave-ui/skills/ui-testing/SKILL.md +716 -0
- package/plugins/specweave-ui/skills/visual-regression/SKILL.md +728 -0
- package/plugins/specweave/commands/check-hooks.md +0 -257
- package/plugins/specweave/commands/specweave-archive-increments.md +0 -82
- package/plugins/specweave/skills/plugin-expert/SKILL.md +0 -340
- /package/plugins/specweave/{agents/code-reviewer.md ā skills/code-reviewer/SKILL.md} +0 -0
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import inquirer from "inquirer";
|
|
3
|
+
import { existsSync } from "fs";
|
|
4
|
+
import path from "path";
|
|
5
|
+
import { JiraClient } from "../../../src/integrations/jira/jira-client.js";
|
|
6
|
+
import { FilterProcessor } from "../../../src/integrations/jira/filter-processor.js";
|
|
7
|
+
import { mergeEnvList, getEnvValue } from "../../../src/utils/env-manager.js";
|
|
8
|
+
import { consoleLogger } from "../../../src/utils/logger.js";
|
|
9
|
+
import { credentialsManager } from "../../../src/core/credentials-manager.js";
|
|
10
|
+
async function importProjects(options = {}) {
|
|
11
|
+
const logger = options.logger ?? consoleLogger;
|
|
12
|
+
const projectRoot = process.cwd();
|
|
13
|
+
console.log(chalk.cyan("\n\u{1F4E5} JIRA Project Import\n"));
|
|
14
|
+
const credentials = credentialsManager.getJiraCredentials();
|
|
15
|
+
if (!credentials) {
|
|
16
|
+
console.log(chalk.red("\u274C No JIRA credentials found"));
|
|
17
|
+
console.log(chalk.gray(" Run: specweave init"));
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
const client = new JiraClient(credentials);
|
|
21
|
+
const filterProcessor = new FilterProcessor(client, { logger });
|
|
22
|
+
if (options.resume) {
|
|
23
|
+
const resumed = await resumeImport(projectRoot, client, filterProcessor, options);
|
|
24
|
+
if (resumed) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
console.log(chalk.yellow("\u26A0\uFE0F No import state found. Starting fresh import.\n"));
|
|
28
|
+
}
|
|
29
|
+
const existing = await loadExistingProjects(projectRoot, logger);
|
|
30
|
+
console.log(chalk.gray(`Current projects: ${existing.length > 0 ? existing.join(", ") : "none"}
|
|
31
|
+
`));
|
|
32
|
+
console.log(chalk.cyan("\u{1F4E1} Fetching available JIRA projects...\n"));
|
|
33
|
+
let allProjects = [];
|
|
34
|
+
try {
|
|
35
|
+
const response = await client.searchProjects({ maxResults: 1e3 });
|
|
36
|
+
allProjects = response.values || [];
|
|
37
|
+
console.log(chalk.green(`\u2713 Found ${allProjects.length} total projects
|
|
38
|
+
`));
|
|
39
|
+
} catch (error) {
|
|
40
|
+
console.log(chalk.red(`\u274C Failed to fetch projects: ${error.message}`));
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
let filteredProjects = allProjects;
|
|
44
|
+
if (options.preset) {
|
|
45
|
+
console.log(chalk.cyan(`\u{1F50D} Applying preset: ${options.preset}
|
|
46
|
+
`));
|
|
47
|
+
try {
|
|
48
|
+
filteredProjects = await filterProcessor.applyPreset(allProjects, options.preset);
|
|
49
|
+
} catch (error) {
|
|
50
|
+
console.log(chalk.red(`\u274C ${error.message}`));
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
} else if (options.filter || options.type || options.lead || options.jql) {
|
|
54
|
+
const filterOptions = {};
|
|
55
|
+
if (options.filter === "active") {
|
|
56
|
+
filterOptions.active = true;
|
|
57
|
+
} else if (options.filter === "archived") {
|
|
58
|
+
filterOptions.active = false;
|
|
59
|
+
}
|
|
60
|
+
if (options.type) {
|
|
61
|
+
filterOptions.types = options.type;
|
|
62
|
+
}
|
|
63
|
+
if (options.lead) {
|
|
64
|
+
filterOptions.lead = options.lead;
|
|
65
|
+
}
|
|
66
|
+
if (options.jql) {
|
|
67
|
+
filterOptions.jql = options.jql;
|
|
68
|
+
}
|
|
69
|
+
console.log(chalk.cyan("\u{1F50D} Applying filters...\n"));
|
|
70
|
+
filteredProjects = await filterProcessor.applyFilters(allProjects, filterOptions);
|
|
71
|
+
}
|
|
72
|
+
const newProjects = filteredProjects.filter((p) => {
|
|
73
|
+
return !existing.some((e) => e.toLowerCase() === p.key.toLowerCase());
|
|
74
|
+
});
|
|
75
|
+
if (newProjects.length === 0) {
|
|
76
|
+
console.log(chalk.yellow("\u26A0\uFE0F No new projects found to import"));
|
|
77
|
+
console.log(chalk.gray(" All available projects are already imported\n"));
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
console.log(chalk.cyan("\u{1F4CB} Import Preview:\n"));
|
|
81
|
+
console.log(chalk.white(` Total available: ${allProjects.length}`));
|
|
82
|
+
console.log(chalk.white(` After filtering: ${filteredProjects.length}`));
|
|
83
|
+
console.log(chalk.white(` Already imported: ${existing.length}`));
|
|
84
|
+
console.log(chalk.white(` New projects: ${chalk.green.bold(newProjects.length)}
|
|
85
|
+
`));
|
|
86
|
+
if (newProjects.length <= 10) {
|
|
87
|
+
console.log(chalk.gray("Projects to import:"));
|
|
88
|
+
newProjects.forEach((p) => {
|
|
89
|
+
const typeLabel = p.projectTypeKey || "unknown";
|
|
90
|
+
const leadLabel = p.lead?.displayName || "no lead";
|
|
91
|
+
console.log(chalk.gray(` \u2728 ${p.key} - ${p.name} (${typeLabel}, ${leadLabel})`));
|
|
92
|
+
});
|
|
93
|
+
console.log("");
|
|
94
|
+
}
|
|
95
|
+
if (options.dryRun) {
|
|
96
|
+
console.log(chalk.yellow("\u{1F50D} Dry-run mode: No changes will be made\n"));
|
|
97
|
+
console.log(chalk.green(`\u2713 Preview complete: ${newProjects.length} project(s) would be imported
|
|
98
|
+
`));
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
const { confirmed } = await inquirer.prompt([{
|
|
102
|
+
type: "confirm",
|
|
103
|
+
name: "confirmed",
|
|
104
|
+
message: `Import ${newProjects.length} new project(s)?`,
|
|
105
|
+
default: true
|
|
106
|
+
}]);
|
|
107
|
+
if (!confirmed) {
|
|
108
|
+
console.log(chalk.yellow("\n\u23ED\uFE0F Import canceled\n"));
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
const projectKeys = newProjects.map((p) => p.key);
|
|
112
|
+
console.log(chalk.cyan("\n\u{1F4E5} Importing projects...\n"));
|
|
113
|
+
try {
|
|
114
|
+
await mergeEnvList({
|
|
115
|
+
key: "JIRA_PROJECTS",
|
|
116
|
+
newValues: projectKeys,
|
|
117
|
+
projectRoot,
|
|
118
|
+
logger,
|
|
119
|
+
createBackup: true
|
|
120
|
+
});
|
|
121
|
+
console.log(chalk.green(`
|
|
122
|
+
\u2705 Successfully imported ${projectKeys.length} project(s)
|
|
123
|
+
`));
|
|
124
|
+
console.log(chalk.gray("Updated: .env (JIRA_PROJECTS)"));
|
|
125
|
+
console.log(chalk.gray("Backup: .env.backup\n"));
|
|
126
|
+
} catch (error) {
|
|
127
|
+
console.log(chalk.red(`
|
|
128
|
+
\u274C Import failed: ${error.message}
|
|
129
|
+
`));
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
async function loadExistingProjects(projectRoot, logger) {
|
|
133
|
+
const value = await getEnvValue(projectRoot, "JIRA_PROJECTS");
|
|
134
|
+
if (!value) {
|
|
135
|
+
return [];
|
|
136
|
+
}
|
|
137
|
+
return value.split(",").map((v) => v.trim()).filter((v) => v.length > 0);
|
|
138
|
+
}
|
|
139
|
+
async function resumeImport(projectRoot, client, filterProcessor, options) {
|
|
140
|
+
const stateFile = path.join(projectRoot, ".specweave", "cache", "import-state.json");
|
|
141
|
+
if (!existsSync(stateFile)) {
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
console.log(chalk.cyan("\u{1F504} Resuming interrupted import...\n"));
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
async function main() {
|
|
148
|
+
const args = process.argv.slice(2);
|
|
149
|
+
const options = {};
|
|
150
|
+
for (let i = 0; i < args.length; i++) {
|
|
151
|
+
const arg = args[i];
|
|
152
|
+
if (arg === "--filter") {
|
|
153
|
+
options.filter = args[++i];
|
|
154
|
+
} else if (arg === "--type") {
|
|
155
|
+
options.type = args[++i].split(",");
|
|
156
|
+
} else if (arg === "--lead") {
|
|
157
|
+
options.lead = args[++i];
|
|
158
|
+
} else if (arg === "--jql") {
|
|
159
|
+
options.jql = args[++i];
|
|
160
|
+
} else if (arg === "--preset") {
|
|
161
|
+
options.preset = args[++i];
|
|
162
|
+
} else if (arg === "--dry-run") {
|
|
163
|
+
options.dryRun = true;
|
|
164
|
+
} else if (arg === "--resume") {
|
|
165
|
+
options.resume = true;
|
|
166
|
+
} else if (arg === "--no-progress") {
|
|
167
|
+
options.noProgress = true;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
await importProjects(options);
|
|
171
|
+
}
|
|
172
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
173
|
+
main().catch((error) => {
|
|
174
|
+
console.error(chalk.red(`
|
|
175
|
+
\u274C Error: ${error.message}
|
|
176
|
+
`));
|
|
177
|
+
process.exit(1);
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
export {
|
|
181
|
+
main as default,
|
|
182
|
+
importProjects
|
|
183
|
+
};
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: specweave-jira:import-projects
|
|
3
|
+
description: Import additional JIRA projects post-init with smart filtering, resume, and dry-run support
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# JIRA Project Import Command
|
|
7
|
+
|
|
8
|
+
Import additional JIRA projects after initial setup with advanced filtering and merge capabilities.
|
|
9
|
+
|
|
10
|
+
## Usage
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
# Import all active projects
|
|
14
|
+
/specweave-jira:import-projects --filter active
|
|
15
|
+
|
|
16
|
+
# Import with preset filter
|
|
17
|
+
/specweave-jira:import-projects --preset production
|
|
18
|
+
|
|
19
|
+
# Import with custom JQL
|
|
20
|
+
/specweave-jira:import-projects --jql "project NOT IN (TEST, SANDBOX)"
|
|
21
|
+
|
|
22
|
+
# Dry-run preview (no changes)
|
|
23
|
+
/specweave-jira:import-projects --dry-run
|
|
24
|
+
|
|
25
|
+
# Resume interrupted import
|
|
26
|
+
/specweave-jira:import-projects --resume
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Options
|
|
30
|
+
|
|
31
|
+
- `--filter <type>` - Filter by status (active, archived, all)
|
|
32
|
+
- `--type <types>` - Filter by project type (software, business, service_desk)
|
|
33
|
+
- `--lead <email>` - Filter by project lead
|
|
34
|
+
- `--jql <query>` - Custom JQL filter
|
|
35
|
+
- `--preset <name>` - Use saved filter preset
|
|
36
|
+
- `--dry-run` - Preview without making changes
|
|
37
|
+
- `--resume` - Resume interrupted import
|
|
38
|
+
- `--no-progress` - Disable progress tracking
|
|
39
|
+
|
|
40
|
+
## Examples
|
|
41
|
+
|
|
42
|
+
### Import Active Projects Only
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
/specweave-jira:import-projects --filter active
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Filters out archived projects, shows preview, prompts for confirmation, merges with existing.
|
|
49
|
+
|
|
50
|
+
### Import with Production Preset
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
/specweave-jira:import-projects --preset production
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Uses the "production" preset filter (active + software + excludes TEST/SANDBOX).
|
|
57
|
+
|
|
58
|
+
### Custom JQL Filter
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
/specweave-jira:import-projects --jql "lead = currentUser() AND status != Archived"
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Imports projects where you are the lead and not archived.
|
|
65
|
+
|
|
66
|
+
### Dry-Run Preview
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
/specweave-jira:import-projects --filter active --dry-run
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Shows which projects would be imported without making any changes.
|
|
73
|
+
|
|
74
|
+
## Features
|
|
75
|
+
|
|
76
|
+
- **Smart Filtering**: Filter by status, type, lead, or custom JQL
|
|
77
|
+
- **Merge with Existing**: Automatically merges with existing projects (no duplicates)
|
|
78
|
+
- **Progress Tracking**: Real-time progress with ETA and cancelation support
|
|
79
|
+
- **Resume Support**: Resume interrupted imports with `--resume`
|
|
80
|
+
- **Dry-Run Mode**: Preview changes before applying
|
|
81
|
+
- **Filter Presets**: Use saved filter combinations
|
|
82
|
+
|
|
83
|
+
## Implementation
|
|
84
|
+
|
|
85
|
+
This command:
|
|
86
|
+
|
|
87
|
+
1. Reads existing projects from `.env` (JIRA_PROJECTS)
|
|
88
|
+
2. Fetches available projects from JIRA API
|
|
89
|
+
3. Applies selected filters using FilterProcessor
|
|
90
|
+
4. Shows preview with project count and reduction percentage
|
|
91
|
+
5. Prompts for confirmation
|
|
92
|
+
6. Batch imports with AsyncProjectLoader (50-project limit)
|
|
93
|
+
7. Shows progress with ETA
|
|
94
|
+
8. Merges with existing projects (no duplicates)
|
|
95
|
+
9. Updates `.env` file atomically
|
|
96
|
+
|
|
97
|
+
Handles Ctrl+C gracefully with import state saving for resume.
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JIRA Project Import Command (Post-Init)
|
|
3
|
+
*
|
|
4
|
+
* Import additional JIRA projects after initial setup with smart filtering,
|
|
5
|
+
* resume support, and merge capabilities.
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - Smart filtering (active, type, lead, JQL)
|
|
9
|
+
* - Filter presets (production, active-only, agile-only)
|
|
10
|
+
* - Dry-run preview mode
|
|
11
|
+
* - Resume interrupted imports
|
|
12
|
+
* - Progress tracking with ETA
|
|
13
|
+
* - Merge with existing projects (no duplicates)
|
|
14
|
+
*
|
|
15
|
+
* NEW (v0.24.0): Post-init flexibility for project management
|
|
16
|
+
*
|
|
17
|
+
* @module plugins/specweave-jira/commands/import-projects
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import chalk from 'chalk';
|
|
21
|
+
import inquirer from 'inquirer';
|
|
22
|
+
import { existsSync } from 'fs';
|
|
23
|
+
import path from 'path';
|
|
24
|
+
import { JiraClient } from '../../../src/integrations/jira/jira-client.js';
|
|
25
|
+
import { FilterProcessor, type FilterOptions } from '../../../src/integrations/jira/filter-processor.js';
|
|
26
|
+
import { AsyncProjectLoader } from '../../../src/cli/helpers/async-project-loader.js';
|
|
27
|
+
import { mergeEnvList, getEnvValue } from '../../../src/utils/env-manager.js';
|
|
28
|
+
import { consoleLogger, type Logger } from '../../../src/utils/logger.js';
|
|
29
|
+
import { credentialsManager } from '../../../src/core/credentials-manager.js';
|
|
30
|
+
|
|
31
|
+
export interface ImportProjectsOptions {
|
|
32
|
+
filter?: 'active' | 'archived' | 'all';
|
|
33
|
+
type?: string[];
|
|
34
|
+
lead?: string;
|
|
35
|
+
jql?: string;
|
|
36
|
+
preset?: string;
|
|
37
|
+
dryRun?: boolean;
|
|
38
|
+
resume?: boolean;
|
|
39
|
+
noProgress?: boolean;
|
|
40
|
+
logger?: Logger;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface ImportState {
|
|
44
|
+
total: number;
|
|
45
|
+
completed: string[];
|
|
46
|
+
remaining: string[];
|
|
47
|
+
timestamp: number;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Import JIRA projects command
|
|
52
|
+
*
|
|
53
|
+
* TC-063: Post-Init Import (Merge with Existing)
|
|
54
|
+
* TC-064: Filter Active Projects Only
|
|
55
|
+
* TC-068: Resume Interrupted Import
|
|
56
|
+
* TC-069: Dry-Run Preview
|
|
57
|
+
* TC-070: Progress During Import
|
|
58
|
+
*
|
|
59
|
+
* @param options - Import options
|
|
60
|
+
*/
|
|
61
|
+
export async function importProjects(options: ImportProjectsOptions = {}): Promise<void> {
|
|
62
|
+
const logger = options.logger ?? consoleLogger;
|
|
63
|
+
const projectRoot = process.cwd();
|
|
64
|
+
|
|
65
|
+
// Step 1: Load credentials
|
|
66
|
+
console.log(chalk.cyan('\nš„ JIRA Project Import\n'));
|
|
67
|
+
|
|
68
|
+
const credentials = credentialsManager.getJiraCredentials();
|
|
69
|
+
if (!credentials) {
|
|
70
|
+
console.log(chalk.red('ā No JIRA credentials found'));
|
|
71
|
+
console.log(chalk.gray(' Run: specweave init'));
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const client = new JiraClient(credentials);
|
|
76
|
+
const filterProcessor = new FilterProcessor(client, { logger });
|
|
77
|
+
|
|
78
|
+
// Step 2: Check for resume state
|
|
79
|
+
if (options.resume) {
|
|
80
|
+
const resumed = await resumeImport(projectRoot, client, filterProcessor, options);
|
|
81
|
+
if (resumed) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
console.log(chalk.yellow('ā ļø No import state found. Starting fresh import.\n'));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Step 3: Read existing projects from .env
|
|
88
|
+
const existing = await loadExistingProjects(projectRoot, logger);
|
|
89
|
+
console.log(chalk.gray(`Current projects: ${existing.length > 0 ? existing.join(', ') : 'none'}\n`));
|
|
90
|
+
|
|
91
|
+
// Step 4: Fetch available projects from JIRA
|
|
92
|
+
console.log(chalk.cyan('š” Fetching available JIRA projects...\n'));
|
|
93
|
+
|
|
94
|
+
let allProjects: any[] = [];
|
|
95
|
+
try {
|
|
96
|
+
const response = await client.searchProjects({ maxResults: 1000 });
|
|
97
|
+
allProjects = response.values || [];
|
|
98
|
+
console.log(chalk.green(`ā Found ${allProjects.length} total projects\n`));
|
|
99
|
+
} catch (error: any) {
|
|
100
|
+
console.log(chalk.red(`ā Failed to fetch projects: ${error.message}`));
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Step 5: Apply filters
|
|
105
|
+
let filteredProjects = allProjects;
|
|
106
|
+
|
|
107
|
+
if (options.preset) {
|
|
108
|
+
// Use filter preset
|
|
109
|
+
console.log(chalk.cyan(`š Applying preset: ${options.preset}\n`));
|
|
110
|
+
try {
|
|
111
|
+
filteredProjects = await filterProcessor.applyPreset(allProjects, options.preset);
|
|
112
|
+
} catch (error: any) {
|
|
113
|
+
console.log(chalk.red(`ā ${error.message}`));
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
} else if (options.filter || options.type || options.lead || options.jql) {
|
|
117
|
+
// Build filter options
|
|
118
|
+
const filterOptions: FilterOptions = {};
|
|
119
|
+
|
|
120
|
+
if (options.filter === 'active') {
|
|
121
|
+
filterOptions.active = true;
|
|
122
|
+
} else if (options.filter === 'archived') {
|
|
123
|
+
filterOptions.active = false;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (options.type) {
|
|
127
|
+
filterOptions.types = options.type;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (options.lead) {
|
|
131
|
+
filterOptions.lead = options.lead;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (options.jql) {
|
|
135
|
+
filterOptions.jql = options.jql;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
console.log(chalk.cyan('š Applying filters...\n'));
|
|
139
|
+
filteredProjects = await filterProcessor.applyFilters(allProjects, filterOptions);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Step 6: Exclude existing projects
|
|
143
|
+
const newProjects = filteredProjects.filter(p => {
|
|
144
|
+
return !existing.some(e => e.toLowerCase() === p.key.toLowerCase());
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
if (newProjects.length === 0) {
|
|
148
|
+
console.log(chalk.yellow('ā ļø No new projects found to import'));
|
|
149
|
+
console.log(chalk.gray(' All available projects are already imported\n'));
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Step 7: Show preview
|
|
154
|
+
console.log(chalk.cyan('š Import Preview:\n'));
|
|
155
|
+
console.log(chalk.white(` Total available: ${allProjects.length}`));
|
|
156
|
+
console.log(chalk.white(` After filtering: ${filteredProjects.length}`));
|
|
157
|
+
console.log(chalk.white(` Already imported: ${existing.length}`));
|
|
158
|
+
console.log(chalk.white(` New projects: ${chalk.green.bold(newProjects.length)}\n`));
|
|
159
|
+
|
|
160
|
+
if (newProjects.length <= 10) {
|
|
161
|
+
console.log(chalk.gray('Projects to import:'));
|
|
162
|
+
newProjects.forEach(p => {
|
|
163
|
+
const typeLabel = p.projectTypeKey || 'unknown';
|
|
164
|
+
const leadLabel = p.lead?.displayName || 'no lead';
|
|
165
|
+
console.log(chalk.gray(` ⨠${p.key} - ${p.name} (${typeLabel}, ${leadLabel})`));
|
|
166
|
+
});
|
|
167
|
+
console.log('');
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Step 8: Dry-run mode (exit without changes)
|
|
171
|
+
if (options.dryRun) {
|
|
172
|
+
console.log(chalk.yellow('š Dry-run mode: No changes will be made\n'));
|
|
173
|
+
console.log(chalk.green(`ā Preview complete: ${newProjects.length} project(s) would be imported\n`));
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Step 9: Confirm import
|
|
178
|
+
const { confirmed } = await inquirer.prompt([{
|
|
179
|
+
type: 'confirm',
|
|
180
|
+
name: 'confirmed',
|
|
181
|
+
message: `Import ${newProjects.length} new project(s)?`,
|
|
182
|
+
default: true
|
|
183
|
+
}]);
|
|
184
|
+
|
|
185
|
+
if (!confirmed) {
|
|
186
|
+
console.log(chalk.yellow('\nāļø Import canceled\n'));
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Step 10: Extract project keys
|
|
191
|
+
const projectKeys = newProjects.map(p => p.key);
|
|
192
|
+
|
|
193
|
+
// Step 11: Merge with existing
|
|
194
|
+
console.log(chalk.cyan('\nš„ Importing projects...\n'));
|
|
195
|
+
|
|
196
|
+
try {
|
|
197
|
+
await mergeEnvList({
|
|
198
|
+
key: 'JIRA_PROJECTS',
|
|
199
|
+
newValues: projectKeys,
|
|
200
|
+
projectRoot,
|
|
201
|
+
logger,
|
|
202
|
+
createBackup: true
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
console.log(chalk.green(`\nā
Successfully imported ${projectKeys.length} project(s)\n`));
|
|
206
|
+
console.log(chalk.gray('Updated: .env (JIRA_PROJECTS)'));
|
|
207
|
+
console.log(chalk.gray('Backup: .env.backup\n'));
|
|
208
|
+
|
|
209
|
+
} catch (error: any) {
|
|
210
|
+
console.log(chalk.red(`\nā Import failed: ${error.message}\n`));
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Load existing JIRA projects from .env
|
|
216
|
+
*/
|
|
217
|
+
async function loadExistingProjects(projectRoot: string, logger: Logger): Promise<string[]> {
|
|
218
|
+
const value = await getEnvValue(projectRoot, 'JIRA_PROJECTS');
|
|
219
|
+
if (!value) {
|
|
220
|
+
return [];
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return value.split(',').map(v => v.trim()).filter(v => v.length > 0);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Resume interrupted import
|
|
228
|
+
*/
|
|
229
|
+
async function resumeImport(
|
|
230
|
+
projectRoot: string,
|
|
231
|
+
client: JiraClient,
|
|
232
|
+
filterProcessor: FilterProcessor,
|
|
233
|
+
options: ImportProjectsOptions
|
|
234
|
+
): Promise<boolean> {
|
|
235
|
+
const stateFile = path.join(projectRoot, '.specweave', 'cache', 'import-state.json');
|
|
236
|
+
|
|
237
|
+
if (!existsSync(stateFile)) {
|
|
238
|
+
return false;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
console.log(chalk.cyan('š Resuming interrupted import...\n'));
|
|
242
|
+
|
|
243
|
+
// Load state and continue
|
|
244
|
+
// (Implementation would load state, skip completed, import remaining)
|
|
245
|
+
|
|
246
|
+
return false; // Placeholder - full implementation would handle resume
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Command entry point (called by CLI)
|
|
251
|
+
*/
|
|
252
|
+
export default async function main(): Promise<void> {
|
|
253
|
+
// Parse CLI arguments
|
|
254
|
+
const args = process.argv.slice(2);
|
|
255
|
+
const options: ImportProjectsOptions = {};
|
|
256
|
+
|
|
257
|
+
for (let i = 0; i < args.length; i++) {
|
|
258
|
+
const arg = args[i];
|
|
259
|
+
|
|
260
|
+
if (arg === '--filter') {
|
|
261
|
+
options.filter = args[++i] as 'active' | 'archived' | 'all';
|
|
262
|
+
} else if (arg === '--type') {
|
|
263
|
+
options.type = args[++i].split(',');
|
|
264
|
+
} else if (arg === '--lead') {
|
|
265
|
+
options.lead = args[++i];
|
|
266
|
+
} else if (arg === '--jql') {
|
|
267
|
+
options.jql = args[++i];
|
|
268
|
+
} else if (arg === '--preset') {
|
|
269
|
+
options.preset = args[++i];
|
|
270
|
+
} else if (arg === '--dry-run') {
|
|
271
|
+
options.dryRun = true;
|
|
272
|
+
} else if (arg === '--resume') {
|
|
273
|
+
options.resume = true;
|
|
274
|
+
} else if (arg === '--no-progress') {
|
|
275
|
+
options.noProgress = true;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
await importProjects(options);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Run command if called directly
|
|
283
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
284
|
+
main().catch(error => {
|
|
285
|
+
console.error(chalk.red(`\nā Error: ${error.message}\n`));
|
|
286
|
+
process.exit(1);
|
|
287
|
+
});
|
|
288
|
+
}
|