@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.
- package/dist/action/index.js +1 -1
- package/dist/action.js +60 -0
- package/dist/adapters/github.js +467 -0
- package/dist/adapters/resources.js +363 -0
- package/dist/cli/index.js +3 -3
- package/dist/commands/apply.js +390 -0
- package/dist/commands/destroy.js +85 -0
- package/dist/commands/diff.js +131 -0
- package/dist/commands/fmt.js +166 -0
- package/dist/commands/force-unlock.js +55 -0
- package/dist/commands/generate.js +143 -0
- package/dist/commands/graph.js +159 -0
- package/dist/commands/import.js +222 -0
- package/dist/commands/init.js +167 -0
- package/dist/commands/login.js +71 -0
- package/dist/commands/logout.js +20 -0
- package/dist/commands/plan.js +250 -0
- package/dist/commands/refresh.js +165 -0
- package/dist/commands/report.js +724 -0
- package/dist/commands/show.js +61 -0
- package/dist/commands/state.js +197 -0
- package/dist/commands/taint.js +49 -0
- package/dist/commands/validate.js +128 -0
- package/dist/commands/workspace.js +102 -0
- package/dist/const.js +105 -0
- package/dist/core/backends/azurerm.js +201 -0
- package/dist/core/backends/backend.js +2 -0
- package/dist/core/backends/gcs.js +200 -0
- package/dist/core/backends/local.js +162 -0
- package/dist/core/backends/s3.js +224 -0
- package/dist/core/command-context.js +59 -0
- package/dist/core/config.js +131 -0
- package/dist/core/credentials.js +53 -0
- package/dist/core/parser.js +62 -0
- package/dist/core/parsers/base-parser.js +215 -0
- package/dist/core/parsers/testcase-parser.js +115 -0
- package/dist/core/parsers/testplan-parser.js +41 -0
- package/dist/core/parsers/testrun-parser.js +43 -0
- package/dist/core/policy.js +341 -0
- package/dist/core/prompt.js +109 -0
- package/dist/core/state.js +185 -0
- package/dist/core/utils.js +94 -0
- package/dist/core/variables.js +108 -0
- package/dist/core/workspace.js +56 -0
- package/dist/help.js +797 -0
- package/dist/index.js +650 -0
- package/dist/logger.js +134 -0
- package/dist/notify.js +36 -0
- package/dist/types.js +2 -0
- 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;
|