@solidactions/cli 1.23.0 → 1.23.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/commands/login.js +27 -4
- package/dist/utils/config-write-target.js +25 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -44,6 +44,8 @@ You can mix: e.g., set only `SOLIDACTIONS_WORKSPACE_ID` in the environment while
|
|
|
44
44
|
|
|
45
45
|
In interactive shells, `login` without `--local`/`--global` prompts for a location. In non-interactive contexts, one of the flags is required.
|
|
46
46
|
|
|
47
|
+
If the target config file already exists and its contents would change, `login` writes a timestamped backup (e.g. `config.json.bak-2026-07-05T12-30-00Z`) alongside it before overwriting, and prints the backup path. Non-interactive runs proceed automatically (with the backup); an interactive TTY additionally asks a y/N confirmation first.
|
|
48
|
+
|
|
47
49
|
### `solidactions logout` flags
|
|
48
50
|
|
|
49
51
|
- `--local` — remove only the nearest local config (walks up from cwd).
|
package/dist/commands/login.js
CHANGED
|
@@ -7,10 +7,12 @@ exports.getConfig = getConfig;
|
|
|
7
7
|
exports.saveConfig = saveConfig;
|
|
8
8
|
exports.clearConfig = clearConfig;
|
|
9
9
|
exports.resolveLoginHost = resolveLoginHost;
|
|
10
|
+
exports.backupPathFor = backupPathFor;
|
|
10
11
|
exports.loginHostLines = loginHostLines;
|
|
11
12
|
exports.login = login;
|
|
12
13
|
exports.logout = logout;
|
|
13
14
|
exports.whoami = whoami;
|
|
15
|
+
const fs_1 = __importDefault(require("fs"));
|
|
14
16
|
const chalk_1 = __importDefault(require("chalk"));
|
|
15
17
|
const api_1 = require("../utils/api");
|
|
16
18
|
const workspaces_1 = require("./workspaces");
|
|
@@ -37,6 +39,18 @@ function resolveLoginHost(options) {
|
|
|
37
39
|
}
|
|
38
40
|
return { host: 'https://app.solidactions.com', isDefault: true };
|
|
39
41
|
}
|
|
42
|
+
/**
|
|
43
|
+
* Path for a timestamped backup of `targetPath`, e.g.
|
|
44
|
+
* `config.json.bak-2026-07-05T12-30-00Z`. Pure — takes `now` so tests are
|
|
45
|
+
* deterministic.
|
|
46
|
+
*/
|
|
47
|
+
function backupPathFor(targetPath, now = new Date()) {
|
|
48
|
+
const timestamp = now.toISOString().replace(/\.\d{3}Z$/, 'Z').replace(/:/g, '-');
|
|
49
|
+
return `${targetPath}.bak-${timestamp}`;
|
|
50
|
+
}
|
|
51
|
+
const LOGIN_REFUSAL_MESSAGE = 'Refusing to write config non-interactively. Pass --global to update the machine-wide config ' +
|
|
52
|
+
'at ~/.solidactions/config.json (a backup is kept if one exists), or --local to write ' +
|
|
53
|
+
'./.solidactions/config.json for just this directory.';
|
|
40
54
|
function loginHostLines(resolved) {
|
|
41
55
|
if (resolved.isDefault) {
|
|
42
56
|
return [
|
|
@@ -55,19 +69,28 @@ async function login(apiKey, options) {
|
|
|
55
69
|
process.exit(1);
|
|
56
70
|
}
|
|
57
71
|
// Determine target location.
|
|
58
|
-
const target = await (0, config_write_target_1.decideWriteTarget)({ local: options.local, global: options.global });
|
|
72
|
+
const target = await (0, config_write_target_1.decideWriteTarget)({ local: options.local, global: options.global }, undefined, LOGIN_REFUSAL_MESSAGE);
|
|
59
73
|
const targetPath = (0, config_write_target_1.pathForTarget)(target);
|
|
60
74
|
console.log(chalk_1.default.blue(`Initializing SolidActions CLI...`));
|
|
61
75
|
for (const line of loginHostLines(resolved)) {
|
|
62
76
|
console.log(resolved.isDefault ? chalk_1.default.yellow(line) : chalk_1.default.gray(line));
|
|
63
77
|
}
|
|
64
|
-
if ((0, config_1.readConfigFile)(targetPath)) {
|
|
65
|
-
console.log(chalk_1.default.yellow(`Existing config at ${targetPath} will be overwritten.`));
|
|
66
|
-
}
|
|
67
78
|
const config = {
|
|
68
79
|
host,
|
|
69
80
|
apiKey: apiKey.trim(),
|
|
70
81
|
};
|
|
82
|
+
const existingRaw = fs_1.default.existsSync(targetPath) ? fs_1.default.readFileSync(targetPath, 'utf-8') : null;
|
|
83
|
+
const wouldChange = existingRaw !== null && existingRaw.trim() !== JSON.stringify(config, null, 2).trim();
|
|
84
|
+
if (wouldChange) {
|
|
85
|
+
const backupPath = backupPathFor(targetPath);
|
|
86
|
+
const proceed = await (0, config_write_target_1.confirmOverwrite)(targetPath, backupPath);
|
|
87
|
+
if (!proceed) {
|
|
88
|
+
console.log(chalk_1.default.yellow('Aborted. No changes were made.'));
|
|
89
|
+
process.exit(0);
|
|
90
|
+
}
|
|
91
|
+
fs_1.default.copyFileSync(targetPath, backupPath);
|
|
92
|
+
console.log(chalk_1.default.gray(`Backup saved to ${backupPath}`));
|
|
93
|
+
}
|
|
71
94
|
(0, config_1.writeConfigFile)(targetPath, config);
|
|
72
95
|
if (target === 'local') {
|
|
73
96
|
await (0, config_write_target_1.ensureGitignoreCovers)(process.cwd(), !!options.gitignore);
|
|
@@ -5,6 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.decideWriteTarget = decideWriteTarget;
|
|
7
7
|
exports.pathForTarget = pathForTarget;
|
|
8
|
+
exports.confirmOverwrite = confirmOverwrite;
|
|
8
9
|
exports.ensureGitignoreCovers = ensureGitignoreCovers;
|
|
9
10
|
const fs_1 = __importDefault(require("fs"));
|
|
10
11
|
const path_1 = __importDefault(require("path"));
|
|
@@ -19,7 +20,7 @@ const config_1 = require("./config");
|
|
|
19
20
|
* - Else if TTY, prompt.
|
|
20
21
|
* - Else error and exit.
|
|
21
22
|
*/
|
|
22
|
-
async function decideWriteTarget(options, promptLabel = 'Save config locally (./.solidactions) or globally (~/.solidactions)? [global] ') {
|
|
23
|
+
async function decideWriteTarget(options, promptLabel = 'Save config locally (./.solidactions) or globally (~/.solidactions)? [global] ', refusalMessage = 'Refusing to write config non-interactively. Pass --local or --global.') {
|
|
23
24
|
if (options.local && options.global) {
|
|
24
25
|
console.error(chalk_1.default.red('Error: --local and --global are mutually exclusive.'));
|
|
25
26
|
process.exit(1);
|
|
@@ -45,12 +46,34 @@ async function decideWriteTarget(options, promptLabel = 'Save config locally (./
|
|
|
45
46
|
rl.close();
|
|
46
47
|
}
|
|
47
48
|
}
|
|
48
|
-
console.error(chalk_1.default.red(
|
|
49
|
+
console.error(chalk_1.default.red(refusalMessage));
|
|
49
50
|
process.exit(1);
|
|
50
51
|
}
|
|
51
52
|
function pathForTarget(target) {
|
|
52
53
|
return target === 'local' ? (0, config_1.getLocalConfigPath)() : (0, config_1.getGlobalConfigPath)();
|
|
53
54
|
}
|
|
55
|
+
/**
|
|
56
|
+
* Notify (and, on a TTY, confirm) that an existing config file is about to be
|
|
57
|
+
* overwritten. A backup will be written to `backupPath` regardless of mode —
|
|
58
|
+
* this always says so. Non-interactive callers (CI, agents) proceed
|
|
59
|
+
* automatically so they never wedge; an interactive TTY additionally asks a
|
|
60
|
+
* y/N confirm (default N).
|
|
61
|
+
*/
|
|
62
|
+
async function confirmOverwrite(existingPath, backupPath) {
|
|
63
|
+
const notice = `Existing config at ${existingPath} will be overwritten (backup will be saved to ${backupPath}).`;
|
|
64
|
+
if (!process.stdin.isTTY) {
|
|
65
|
+
console.log(chalk_1.default.yellow(notice));
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
const rl = readline_1.default.createInterface({ input: process.stdin, output: process.stdout });
|
|
69
|
+
try {
|
|
70
|
+
const answer = await new Promise((resolve) => rl.question(chalk_1.default.yellow(`${notice} Continue? [y/N] `), resolve));
|
|
71
|
+
return answer.trim().toLowerCase().startsWith('y');
|
|
72
|
+
}
|
|
73
|
+
finally {
|
|
74
|
+
rl.close();
|
|
75
|
+
}
|
|
76
|
+
}
|
|
54
77
|
/**
|
|
55
78
|
* Ensure `.solidactions/` is in the target directory's `.gitignore`.
|
|
56
79
|
* Idempotent. Skips silently if pattern is already covered.
|