@fromeroc9/testform 1.0.3 → 1.0.5

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.
Files changed (50) hide show
  1. package/dist/action/index.js +1 -1
  2. package/dist/action.js +60 -0
  3. package/dist/adapters/github.js +467 -0
  4. package/dist/adapters/resources.js +363 -0
  5. package/dist/cli/index.js +3 -3
  6. package/dist/commands/apply.js +390 -0
  7. package/dist/commands/destroy.js +85 -0
  8. package/dist/commands/diff.js +131 -0
  9. package/dist/commands/fmt.js +166 -0
  10. package/dist/commands/force-unlock.js +55 -0
  11. package/dist/commands/generate.js +143 -0
  12. package/dist/commands/graph.js +159 -0
  13. package/dist/commands/import.js +222 -0
  14. package/dist/commands/init.js +167 -0
  15. package/dist/commands/login.js +71 -0
  16. package/dist/commands/logout.js +20 -0
  17. package/dist/commands/plan.js +250 -0
  18. package/dist/commands/refresh.js +165 -0
  19. package/dist/commands/report.js +724 -0
  20. package/dist/commands/show.js +61 -0
  21. package/dist/commands/state.js +197 -0
  22. package/dist/commands/taint.js +49 -0
  23. package/dist/commands/validate.js +128 -0
  24. package/dist/commands/workspace.js +102 -0
  25. package/dist/const.js +105 -0
  26. package/dist/core/backends/azurerm.js +201 -0
  27. package/dist/core/backends/backend.js +2 -0
  28. package/dist/core/backends/gcs.js +200 -0
  29. package/dist/core/backends/local.js +162 -0
  30. package/dist/core/backends/s3.js +224 -0
  31. package/dist/core/command-context.js +59 -0
  32. package/dist/core/config.js +131 -0
  33. package/dist/core/credentials.js +53 -0
  34. package/dist/core/parser.js +62 -0
  35. package/dist/core/parsers/base-parser.js +215 -0
  36. package/dist/core/parsers/testcase-parser.js +115 -0
  37. package/dist/core/parsers/testplan-parser.js +41 -0
  38. package/dist/core/parsers/testrun-parser.js +43 -0
  39. package/dist/core/policy.js +341 -0
  40. package/dist/core/prompt.js +109 -0
  41. package/dist/core/state.js +185 -0
  42. package/dist/core/utils.js +94 -0
  43. package/dist/core/variables.js +108 -0
  44. package/dist/core/workspace.js +56 -0
  45. package/dist/help.js +797 -0
  46. package/dist/index.js +650 -0
  47. package/dist/logger.js +134 -0
  48. package/dist/notify.js +36 -0
  49. package/dist/types.js +2 -0
  50. package/package.json +1 -1
@@ -0,0 +1,222 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.importCmd = void 0;
7
+ const path_1 = require("path");
8
+ const config_1 = require("../core/config");
9
+ const state_1 = require("../core/state");
10
+ const github_1 = require("../adapters/github");
11
+ const parser_1 = require("../core/parser");
12
+ const resources_1 = require("../adapters/resources");
13
+ const chalk_1 = require("chalk");
14
+ const plan_1 = require("./plan");
15
+ const fs_1 = __importDefault(require("fs"));
16
+ const logger_1 = require("../logger");
17
+ const const_1 = require("../const");
18
+ const importCmd = async (options) => {
19
+ const { dir = '.', scope, identityArg, issueNumber, lock = true, lockTimeout = '0s', statePath, backupPath } = options;
20
+ const logger = new logger_1.Logger();
21
+ console.log((0, chalk_1.bold)(`Importing ${scope} ${identityArg} from issue #${issueNumber}...`));
22
+ const stateObj = new state_1.State(dir, statePath, backupPath);
23
+ await stateObj.init();
24
+ await stateObj.acquireLock(lock, lockTimeout);
25
+ try {
26
+ const state = stateObj;
27
+ const basePath = (0, path_1.resolve)(dir);
28
+ const configPath = (0, path_1.normalize)((0, path_1.join)(basePath, const_1.FILE_CONFIG));
29
+ if (!configPath.startsWith(basePath)) {
30
+ console.error((0, chalk_1.red)(`Invalid configuration path.`));
31
+ process.exit(1);
32
+ }
33
+ if (!fs_1.default.existsSync(configPath)) {
34
+ console.error((0, chalk_1.red)(`Configuration file ${const_1.FILE_CONFIG} not found in directory.`));
35
+ process.exit(1);
36
+ }
37
+ const config = JSON.parse(fs_1.default.readFileSync(configPath, 'utf-8'));
38
+ const github = new github_1.GitHubAdapter(config.github);
39
+ // Validate issueNumber
40
+ const issueNum = parseInt(issueNumber, 10);
41
+ if (isNaN(issueNum)) {
42
+ console.error((0, chalk_1.red)(`Invalid issue number: ${issueNumber}`));
43
+ process.exit(1);
44
+ }
45
+ // If short identity, scan scenarios to find the matching one
46
+ let identity = identityArg;
47
+ const conf = new config_1.Config(dir);
48
+ const data = {
49
+ identity: conf.getIdentity(scope),
50
+ fields: conf.getFields(scope),
51
+ };
52
+ const matchesScope = (s, scopeName) => {
53
+ if (!Object.prototype.hasOwnProperty.call(const_1.SCOPE_CONFIG, scopeName))
54
+ return false;
55
+ const cfg = const_1.SCOPE_CONFIG[scopeName];
56
+ return s.feature?.tags?.includes(cfg.tag) || s.uri.endsWith(cfg.ext);
57
+ };
58
+ // Read all scenarios using parser
59
+ const parser = new parser_1.Parser(dir);
60
+ const documents = parser.content();
61
+ const rawScenarios = documents.filter(s => matchesScope(s, scope));
62
+ let filtered = parser.filter(rawScenarios, data, scope) || [];
63
+ // Attempt to resolve identity
64
+ const matchedScenario = filtered.find(s => {
65
+ const rawId = s.custom?.identity;
66
+ if (!rawId)
67
+ return false;
68
+ const fullId = (scope === 'testrun' || scope === 'testplan')
69
+ ? rawId
70
+ : `${s.uri}::${rawId}`;
71
+ // Exact match or ends with the arg (e.g. test1.run.feature or tc1.case.feature::@[1])
72
+ return fullId === identityArg || fullId.endsWith(identityArg) || identityArg.endsWith(fullId);
73
+ });
74
+ if (matchedScenario) {
75
+ const rawId = matchedScenario.custom?.identity;
76
+ identity = (scope === 'testrun' || scope === 'testplan')
77
+ ? (rawId || identityArg)
78
+ : `${matchedScenario.uri}::${rawId}`;
79
+ console.log(`Resolved short identity to full identity: ${identity}`);
80
+ }
81
+ else {
82
+ console.log((0, chalk_1.yellow)(`Warning: Could not find matching local scenario for identity: ${identityArg}`));
83
+ console.log((0, chalk_1.yellow)(`Import will proceed with the exact identity provided, but it might not map to any local file.`));
84
+ }
85
+ // Fetch from GitHub
86
+ console.log(`Fetching issue #${issueNum} from GitHub...`);
87
+ const issue = await github.getIssue(issueNum);
88
+ if (!issue) {
89
+ console.error((0, chalk_1.red)(`Issue #${issueNum} not found in GitHub repository.`));
90
+ process.exit(1);
91
+ }
92
+ if (issue.state === 'closed') {
93
+ console.log((0, chalk_1.yellow)(`Warning: Issue #${issueNum} is currently closed.`));
94
+ }
95
+ if (!Object.prototype.hasOwnProperty.call(const_1.SCOPE_RESOURCE_MAP, scope)) {
96
+ console.error((0, chalk_1.red)(`Invalid scope: ${scope}`));
97
+ process.exit(1);
98
+ }
99
+ const resourceType = const_1.SCOPE_RESOURCE_MAP[scope];
100
+ const remoteId = github.formatRemoteId(issueNum);
101
+ // Fetch custom fields
102
+ let customFields = {};
103
+ if (issue.node_id) {
104
+ customFields = await github.getProjectItemFields(issue.node_id);
105
+ }
106
+ // Auto-reconstruct code if not matched
107
+ if (!matchedScenario && scope === 'testcase' && identity.includes('::')) {
108
+ const [rawFilePath, tag] = identity.split('::');
109
+ const filePath = (0, path_1.normalize)((0, path_1.resolve)(basePath, rawFilePath));
110
+ if (!filePath.startsWith(basePath)) {
111
+ console.error((0, chalk_1.red)(`Invalid file path in identity.`));
112
+ process.exit(1);
113
+ }
114
+ if (fs_1.default.existsSync(filePath)) {
115
+ // Find sibling to get global context
116
+ const sibling = rawScenarios.find(s => s.uri === filePath);
117
+ const globalTags = sibling?.feature?.tags || [];
118
+ const bgFields = new Map();
119
+ const fileContent = fs_1.default.readFileSync(filePath, 'utf-8');
120
+ const bgMatch = fileContent.match(/Background:([\s\S]*?)(?=\n\s*@|\n\s*Scenario:|$)/);
121
+ if (bgMatch) {
122
+ const bgText = bgMatch[1];
123
+ for (const line of bgText.split('\n')) {
124
+ const match = line.match(/^\s*(?:\*|Given|When|Then|And)\s+field\s+([A-Za-z0-9_.\- ]+?)\s*=\s*(.+)$/i);
125
+ if (match) {
126
+ bgFields.set(match[1].trim().toLowerCase(), match[2].trim());
127
+ }
128
+ }
129
+ }
130
+ const fieldDefs = conf.getFields(scope) || [];
131
+ const tagsToAppend = [];
132
+ const keywordsToAppend = [];
133
+ // Process Labels
134
+ const issueLabels = issue.labels || [];
135
+ const labelsDef = fieldDefs.find(f => f.name === 'labels');
136
+ const defaultLabel = labelsDef?.default ? String(labelsDef.default) : '';
137
+ for (const l of issueLabels) {
138
+ if (globalTags.includes(l) || globalTags.includes(`@${l}`))
139
+ continue;
140
+ if (l === defaultLabel)
141
+ continue;
142
+ tagsToAppend.push(`@${l}`);
143
+ }
144
+ // Process Custom Fields
145
+ for (const def of fieldDefs) {
146
+ if (def.name === 'labels' || def.name === 'assignees' || def.name === 'milestone')
147
+ continue;
148
+ const remoteKeyMatch = Object.keys(customFields).find(k => k.toLowerCase() === def.name.toLowerCase());
149
+ const remoteVal = remoteKeyMatch ? customFields[remoteKeyMatch] : '';
150
+ if (!remoteVal)
151
+ continue; // Not set in remote
152
+ const defDefault = def.default ? String(def.default) : '';
153
+ // Case-insensitive comparisons
154
+ const isDefault = remoteVal.toLowerCase() === defDefault.toLowerCase();
155
+ const bgVal = bgFields.get(def.name.toLowerCase());
156
+ const isBg = bgVal && bgVal.toLowerCase() === remoteVal.toLowerCase();
157
+ if (isDefault || isBg)
158
+ continue;
159
+ if (def.type === 'tags') {
160
+ // For tags, check if the remote value (e.g. '@high' or 'high') is already in global tags
161
+ const cleanRemoteTag = remoteVal.startsWith('@') ? remoteVal : `@${remoteVal}`;
162
+ if (!globalTags.includes(cleanRemoteTag) && !globalTags.includes(cleanRemoteTag.substring(1))) {
163
+ tagsToAppend.push(cleanRemoteTag);
164
+ }
165
+ }
166
+ else if (def.type === 'keywords') {
167
+ keywordsToAppend.push(`* field ${def.name} = ${remoteVal}`);
168
+ }
169
+ }
170
+ let gherkinBody = issue.body || '';
171
+ const match = gherkinBody.match(/```gherkin\n([\s\S]*?)```/);
172
+ if (match) {
173
+ gherkinBody = match[1].trim();
174
+ }
175
+ else {
176
+ gherkinBody = gherkinBody.trim();
177
+ }
178
+ if (gherkinBody) {
179
+ const tagLine = tagsToAppend.length > 0 ? ` ${tag} ${tagsToAppend.join(' ')}\n` : ` ${tag}\n`;
180
+ const keywordLines = keywordsToAppend.length > 0 ? keywordsToAppend.map(k => ` ${k}\n`).join('') : '';
181
+ const bodyLines = gherkinBody.split('\n').map(l => ` ${l}`).join('\n') + `\n`;
182
+ const scenarioContent = `\n\n${tagLine} Scenario: ${issue.title}\n${keywordLines}${bodyLines}`;
183
+ fs_1.default.appendFileSync(filePath, scenarioContent);
184
+ console.log((0, chalk_1.green)(`\nAutomatically reconstructed scenario code in ${filePath}`));
185
+ }
186
+ }
187
+ }
188
+ // Calculate local hash if a matching scenario was found, otherwise empty hash
189
+ let localHash = '';
190
+ if (matchedScenario) {
191
+ localHash = (0, plan_1.hashScenario)(matchedScenario);
192
+ }
193
+ const payload = matchedScenario ? resources_1.resource.evaluate(resourceType, matchedScenario, { state: stateObj }) : null;
194
+ const p = payload;
195
+ // Save to state
196
+ state.upsertResource({
197
+ type: resourceType,
198
+ identity,
199
+ attributes: {
200
+ title: issue.title,
201
+ body: issue.body || '',
202
+ labels: issue.labels || [],
203
+ assignees: issue.assignees || [],
204
+ milestone: issue.milestone || '',
205
+ custom_fields: customFields,
206
+ remoteId,
207
+ issueNumber: issue.number,
208
+ localHash
209
+ },
210
+ lastApplied: new Date().toISOString()
211
+ });
212
+ state.save();
213
+ console.log((0, chalk_1.green)(`\nImport successful!`));
214
+ console.log(`State updated for ${(0, chalk_1.bold)(`${resourceType}.${identity}`)} -> Issue #${issueNum}`);
215
+ console.log(`Run 'plan' to see if any local changes need to be applied.`);
216
+ }
217
+ finally {
218
+ await stateObj.save();
219
+ await stateObj.releaseLock();
220
+ }
221
+ };
222
+ exports.importCmd = importCmd;
@@ -0,0 +1,167 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.initCmd = void 0;
4
+ const const_1 = require("../const");
5
+ const prompt_1 = require("../core/prompt");
6
+ const fs_1 = require("fs");
7
+ const config_1 = require("../core/config");
8
+ const logger_1 = require("../logger");
9
+ const path_1 = require("path");
10
+ const state_1 = require("../core/state");
11
+ const workspace_1 = require("../core/workspace");
12
+ const resolvePaths = (dir) => ({
13
+ configPath: (0, path_1.join)(dir, const_1.FILE_CONFIG),
14
+ statePath: (0, path_1.join)(dir, const_1.FILE_STATE),
15
+ });
16
+ const ensureWorkingDirectory = (dir, logger) => {
17
+ if ((0, fs_1.existsSync)(dir))
18
+ return;
19
+ (0, fs_1.mkdirSync)(dir, { recursive: true });
20
+ logger.info(`Created ${dir} directory`);
21
+ };
22
+ const createConfigIfNotExists = (configPath, logger) => {
23
+ if ((0, fs_1.existsSync)(configPath)) {
24
+ return true;
25
+ }
26
+ const initialConfig = {
27
+ version: const_1.VERSION_CONFIG,
28
+ github: {
29
+ owner: "<required>",
30
+ repository: "<required>"
31
+ },
32
+ backend: {
33
+ type: "local",
34
+ config: {}
35
+ },
36
+ scope: {
37
+ testcase: { fields: [] },
38
+ testrun: { fields: [] },
39
+ testplan: { fields: [] }
40
+ }
41
+ };
42
+ (0, fs_1.writeFileSync)(configPath, JSON.stringify(initialConfig, null, 2), 'utf-8');
43
+ return false;
44
+ };
45
+ const ensureStateExists = (statePath, logger) => {
46
+ if ((0, fs_1.existsSync)(statePath))
47
+ return;
48
+ const state = {
49
+ version: const_1.VERSION_STATE,
50
+ lastSync: new Date().toISOString(),
51
+ testcase: [],
52
+ testrun: [],
53
+ testplan: [],
54
+ };
55
+ (0, fs_1.writeFileSync)(statePath, JSON.stringify(state, null, 2), 'utf-8');
56
+ };
57
+ const loadValidatedConfig = (dir) => {
58
+ const config = new config_1.Config(dir);
59
+ return config;
60
+ };
61
+ const printInitSummary = (logger, config) => {
62
+ logger.info(`- Configuration: ${const_1.FILE_CONFIG} (v${config.getConfig().version})`);
63
+ logger.info(`- State backend: ready`);
64
+ logger.blank();
65
+ };
66
+ const printNextSteps = (logger) => {
67
+ logger.success(`${const_1.TITLE_APP} has been successfully initialized!`, { bold: true });
68
+ logger.blank();
69
+ logger.info(`You may now begin working with ${const_1.TITLE_APP}. Try running "${const_1.TITLE_CLI} plan" to see`);
70
+ logger.info('any changes that are required for your test management resources.');
71
+ logger.blank();
72
+ logger.info(`If you ever set or change configuration for ${const_1.TITLE_APP}, rerun this command to`);
73
+ logger.info('reinitialize your working directory.');
74
+ };
75
+ const initCmd = async (options = {}) => {
76
+ const { dir = '.', verbose = false, backendConfigRaw, lock = true, lockTimeout = '0s', reconfigure = false, backendEnabled = true, isJson = false } = options;
77
+ const logger = new logger_1.Logger(verbose, isJson);
78
+ const paths = resolvePaths(dir);
79
+ // INPUT
80
+ ensureWorkingDirectory(dir, logger);
81
+ const wasAlreadyInitialized = createConfigIfNotExists(paths.configPath, logger);
82
+ // PROCESS
83
+ logger.info('Initializing the backend...', { bold: true });
84
+ logger.info(`Initializing ${const_1.TITLE_APP} configuration...`, { bold: true });
85
+ const config = loadValidatedConfig(dir);
86
+ let backendConfig = config.getBackend();
87
+ if (!backendEnabled) {
88
+ backendConfig = { type: 'local', config: {} };
89
+ }
90
+ const workspaceManager = new workspace_1.WorkspaceManager(dir);
91
+ const activeBackend = workspaceManager.getActiveBackend();
92
+ // Check if backend changed
93
+ let wantsMigration = false;
94
+ const isBackendChanged = activeBackend && backendConfig && JSON.stringify(activeBackend) !== JSON.stringify(backendConfig);
95
+ if (wasAlreadyInitialized && !isBackendChanged && !reconfigure) {
96
+ logger.error([
97
+ `${const_1.TITLE_APP} initialized in not empty directory!`,
98
+ `The directory has ${const_1.TITLE_APP} configuration files. You may begin working`,
99
+ `with ${const_1.TITLE_APP} immediately by creating ${const_1.TITLE_APP} resources.`
100
+ ]);
101
+ return;
102
+ }
103
+ if (isBackendChanged && backendConfig) {
104
+ if (reconfigure) {
105
+ logger.warn('Backend configuration changed. -reconfigure passed, skipping migration.');
106
+ }
107
+ else {
108
+ const inputEnabled = options.inputEnabled ?? true;
109
+ if (!inputEnabled && options.migrateState === undefined) {
110
+ logger.error('Backend configuration changed.\nError: input is disabled and -migrate-state was not passed.');
111
+ return;
112
+ }
113
+ let doMigrate = options.migrateState;
114
+ if (doMigrate === undefined && inputEnabled) {
115
+ doMigrate = await (0, prompt_1.askMigrationApproval)(backendConfig.type);
116
+ }
117
+ if (doMigrate) {
118
+ wantsMigration = true;
119
+ }
120
+ }
121
+ }
122
+ if (!backendConfig || backendConfig.type === 'local') {
123
+ ensureStateExists(paths.statePath, logger);
124
+ }
125
+ const stateObj = new state_1.State(dir, undefined, undefined, !backendEnabled, backendConfigRaw);
126
+ try {
127
+ await stateObj.acquireLock(lock, lockTimeout);
128
+ if (wantsMigration && activeBackend && backendConfig) {
129
+ logger.info(`Migrating state from ${activeBackend.type} to ${backendConfig.type}...`);
130
+ const oldStateObj = new state_1.State(dir, undefined, undefined, false, undefined, activeBackend);
131
+ try {
132
+ // Initialize old backend and grab its state
133
+ await oldStateObj.init();
134
+ const oldData = oldStateObj.getState();
135
+ // Initialize new backend and replace its state
136
+ await stateObj.init();
137
+ stateObj.replaceState(oldData);
138
+ await stateObj.save();
139
+ logger.success(`Successfully migrated state to ${backendConfig.type} backend!`);
140
+ }
141
+ catch (err) {
142
+ logger.error(`Failed to migrate state: ${err.message}`);
143
+ return;
144
+ }
145
+ }
146
+ else {
147
+ await stateObj.init();
148
+ // If it's empty/new, save the initial state structure
149
+ if (stateObj.getState().serial === 0) {
150
+ await stateObj.save();
151
+ }
152
+ }
153
+ // Save active backend in local tracking file
154
+ workspaceManager.setActiveBackend(backendConfig);
155
+ }
156
+ catch (err) {
157
+ logger.error(`Failed to initialize remote backend: ${err.message}`);
158
+ return;
159
+ }
160
+ finally {
161
+ await stateObj.releaseLock();
162
+ }
163
+ // OUTPUT
164
+ printInitSummary(logger, config);
165
+ printNextSteps(logger);
166
+ };
167
+ exports.initCmd = initCmd;
@@ -0,0 +1,71 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.loginCmd = void 0;
37
+ const chalk_1 = require("chalk");
38
+ const readline = __importStar(require("readline"));
39
+ const credentials_1 = require("../core/credentials");
40
+ const loginCmd = async (options = {}) => {
41
+ let { hostname = 'github.com' } = options;
42
+ // In TestForm we default to GitHub
43
+ if (hostname === 'app.terraform.io')
44
+ hostname = 'github.com';
45
+ console.log(`
46
+ TestForm must now request an API token for ${(0, chalk_1.bold)(hostname)}.
47
+ This token will be stored in plain text at ~/.testform.d/credentials.json.
48
+
49
+ If you are logging into GitHub, you can generate a Personal Access Token (classic)
50
+ with the 'repo' scope at: https://github.com/settings/tokens
51
+ `);
52
+ const rl = readline.createInterface({
53
+ input: process.stdin,
54
+ output: process.stdout
55
+ });
56
+ return new Promise((resolve) => {
57
+ rl.question(`Token for ${hostname}: `, (token) => {
58
+ rl.close();
59
+ const trimmed = token.trim();
60
+ if (!trimmed) {
61
+ console.log('\nToken cannot be empty. Login aborted.');
62
+ process.exit(1);
63
+ }
64
+ const creds = new credentials_1.Credentials();
65
+ creds.setToken(hostname, trimmed);
66
+ console.log(`\n${(0, chalk_1.green)('Success!')} Logged in to ${hostname}.`);
67
+ resolve();
68
+ });
69
+ });
70
+ };
71
+ exports.loginCmd = loginCmd;
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.logoutCmd = void 0;
4
+ const chalk_1 = require("chalk");
5
+ const credentials_1 = require("../core/credentials");
6
+ const logoutCmd = async (options = {}) => {
7
+ let { hostname = 'github.com' } = options;
8
+ // In TestForm we default to GitHub
9
+ if (hostname === 'app.terraform.io')
10
+ hostname = 'github.com';
11
+ const creds = new credentials_1.Credentials();
12
+ const removed = creds.removeToken(hostname);
13
+ if (removed) {
14
+ console.log(`\n${(0, chalk_1.green)('Success!')} Removed credentials for ${hostname}.`);
15
+ }
16
+ else {
17
+ console.log(`\n${(0, chalk_1.yellow)('Warning:')} No credentials found for ${hostname}.`);
18
+ }
19
+ };
20
+ exports.logoutCmd = logoutCmd;