@motionode/cli 0.1.0 → 0.1.2
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 +1 -1
- package/bin/motionode.js +16 -3
- package/lib/commands.js +2 -2
- package/lib/config.js +54 -10
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -20,5 +20,5 @@ motionode finish T11-518 # Motionode checks your pushed commits and moves the
|
|
|
20
20
|
- It runs only fixed git operations Motionode can ask for (check out a ticket
|
|
21
21
|
branch, show a diff, commit and push, move changes onto a ticket branch) —
|
|
22
22
|
never arbitrary commands.
|
|
23
|
-
- `--api URL`
|
|
23
|
+
- `motionode use <url>` points every terminal at another Motionode API until `motionode use production`; `--api URL` or `MOTIONODE_API_URL` overrides it for one run. `motionode where` shows the current one.
|
|
24
24
|
- Revoke a computer in Motionode → Settings → Development workflow.
|
package/bin/motionode.js
CHANGED
|
@@ -15,9 +15,13 @@ const HELP = `motionode ${version} — work Motionode tickets with your own loca
|
|
|
15
15
|
motionode work <ticket> start a ticket locally and check out its branches (e.g. T11-518)
|
|
16
16
|
motionode finish <ticket> [--force] [--note] you're done: Motionode checks your pushed commits and moves it on
|
|
17
17
|
motionode status what each linked folder has checked out and changed
|
|
18
|
+
motionode where which Motionode this terminal talks to
|
|
19
|
+
motionode use <url|production> switch which Motionode the CLI talks to (remembered)
|
|
18
20
|
motionode logout disconnect this computer
|
|
19
21
|
|
|
20
|
-
Git stays yours: fetch, commit and push use your own git credentials and identity
|
|
22
|
+
Git stays yours: fetch, commit and push use your own git credentials and identity.
|
|
23
|
+
Talks to production (https://api.motionode.com) unless "motionode use", MOTIONODE_API_URL or --api says otherwise;
|
|
24
|
+
each Motionode keeps its own connection.`;
|
|
21
25
|
|
|
22
26
|
function flags(argv) {
|
|
23
27
|
const out = { _: [] };
|
|
@@ -37,8 +41,8 @@ function flags(argv) {
|
|
|
37
41
|
async function main() {
|
|
38
42
|
const [command, ...rest] = process.argv.slice(2);
|
|
39
43
|
const args = flags(rest);
|
|
40
|
-
|
|
41
|
-
|
|
44
|
+
// --api (or MOTIONODE_API_URL) picks the Motionode — and its own saved connection.
|
|
45
|
+
const config = Config.load(typeof args.api === 'string' ? args.api : null);
|
|
42
46
|
switch (command) {
|
|
43
47
|
case 'connect':
|
|
44
48
|
return Commands.connect(config, { apiUrl: args.api || null, name: args.name || null, service: args.service || null, relogin: !!args.relogin });
|
|
@@ -56,6 +60,15 @@ async function main() {
|
|
|
56
60
|
return Commands.finish(config, args._[0], { force: !!args.force, note: typeof args.note === 'string' ? args.note : null });
|
|
57
61
|
case 'status':
|
|
58
62
|
return Commands.status(config);
|
|
63
|
+
case 'use': {
|
|
64
|
+
const api = Config.useApi(args._[0]);
|
|
65
|
+
const next = Config.load();
|
|
66
|
+
console.log(`✓ Using ${api}${next.token ? '' : ' — run "motionode connect" in a repository folder to connect this computer to it'}`);
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
case 'where':
|
|
70
|
+
console.log(`${config.apiUrl}${config.token ? ` — connected as ${config.user?.name || config.user?.email || 'you'} (${config.machine?.name || 'this computer'})` : ' — not connected'}`);
|
|
71
|
+
return null;
|
|
59
72
|
case 'logout':
|
|
60
73
|
return Commands.logout(config);
|
|
61
74
|
case '--version':
|
package/lib/commands.js
CHANGED
|
@@ -21,8 +21,8 @@ function openBrowser(url) {
|
|
|
21
21
|
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
22
22
|
|
|
23
23
|
/** Device-code login: approve this computer in the browser once. */
|
|
24
|
-
async function login(config, {
|
|
25
|
-
|
|
24
|
+
async function login(config, { name = null } = {}) {
|
|
25
|
+
// config.apiUrl was already chosen (--api / MOTIONODE_API_URL / production).
|
|
26
26
|
const api = client({ ...config, token: null });
|
|
27
27
|
const machineName = name || os.hostname();
|
|
28
28
|
const start = await api.post('/device/start', { machineName, platform: `${process.platform}-${process.arch}` });
|
package/lib/config.js
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* ~/.motionode/
|
|
5
|
-
*
|
|
4
|
+
* ~/.motionode/ — the machine token, the API it talks to, and which local
|
|
5
|
+
* folders are which Motionode repositories. Folder paths never leave this
|
|
6
6
|
* machine except as the label shown to their owner in Motionode.
|
|
7
|
+
*
|
|
8
|
+
* One connection per Motionode: production lives in config.json; any other API
|
|
9
|
+
* (MOTIONODE_API_URL or --api, e.g. a local API behind a tunnel) gets its own
|
|
10
|
+
* config.<host>.json, so switching between them never disconnects the other.
|
|
7
11
|
*/
|
|
8
12
|
|
|
9
13
|
const fs = require('fs');
|
|
@@ -11,27 +15,67 @@ const os = require('os');
|
|
|
11
15
|
const path = require('path');
|
|
12
16
|
|
|
13
17
|
const DIR = process.env.MOTIONODE_HOME || path.join(os.homedir(), '.motionode');
|
|
14
|
-
const
|
|
15
|
-
const DEFAULT_API = process.env.MOTIONODE_API_URL || 'https://api.motionode.com';
|
|
18
|
+
const PRODUCTION_API = 'https://api.motionode.com';
|
|
16
19
|
|
|
17
|
-
function
|
|
20
|
+
function normalizeApi(url) {
|
|
21
|
+
return String(url || '').trim().replace(/\/+$/, '') || null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const CURRENT_FILE = path.join(DIR, 'current.json');
|
|
25
|
+
|
|
26
|
+
/** The Motionode chosen with `motionode use` (stays until changed). */
|
|
27
|
+
function savedApi() {
|
|
28
|
+
try {
|
|
29
|
+
return normalizeApi(JSON.parse(fs.readFileSync(CURRENT_FILE, 'utf8')).apiUrl);
|
|
30
|
+
} catch (_e) {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** `motionode use <url|production>` — which Motionode every terminal talks to. */
|
|
36
|
+
function useApi(url) {
|
|
37
|
+
const next = String(url || '').trim().toLowerCase() === 'production' ? PRODUCTION_API : normalizeApi(url);
|
|
38
|
+
if (!next || !/^https?:\/\//.test(next)) throw new Error('Give a Motionode API address (https://…) or "production".');
|
|
39
|
+
fs.mkdirSync(DIR, { recursive: true });
|
|
40
|
+
if (next === PRODUCTION_API) {
|
|
41
|
+
try { fs.unlinkSync(CURRENT_FILE); } catch (_e) { /* already production */ }
|
|
42
|
+
} else {
|
|
43
|
+
fs.writeFileSync(CURRENT_FILE, JSON.stringify({ apiUrl: next }, null, 2));
|
|
44
|
+
}
|
|
45
|
+
return next;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** The API this run talks to: --api, then MOTIONODE_API_URL, then `motionode use`, then production. */
|
|
49
|
+
function resolveApi(override = null) {
|
|
50
|
+
return normalizeApi(override) || normalizeApi(process.env.MOTIONODE_API_URL) || savedApi() || PRODUCTION_API;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function fileFor(apiUrl) {
|
|
54
|
+
if (normalizeApi(apiUrl) === PRODUCTION_API) return path.join(DIR, 'config.json');
|
|
55
|
+
let host = 'custom';
|
|
56
|
+
try { host = new URL(apiUrl).host; } catch (_e) { /* keep "custom" */ }
|
|
57
|
+
return path.join(DIR, `config.${host.replace(/[^a-zA-Z0-9.-]/g, '_')}.json`);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function load(apiOverride = null) {
|
|
61
|
+
const apiUrl = resolveApi(apiOverride);
|
|
18
62
|
try {
|
|
19
|
-
const raw = JSON.parse(fs.readFileSync(
|
|
63
|
+
const raw = JSON.parse(fs.readFileSync(fileFor(apiUrl), 'utf8'));
|
|
20
64
|
return {
|
|
21
|
-
apiUrl
|
|
65
|
+
apiUrl,
|
|
22
66
|
token: raw.token || null,
|
|
23
67
|
machine: raw.machine || null,
|
|
24
68
|
user: raw.user || null,
|
|
25
69
|
workspaces: Array.isArray(raw.workspaces) ? raw.workspaces : [],
|
|
26
70
|
};
|
|
27
71
|
} catch (_e) {
|
|
28
|
-
return { apiUrl
|
|
72
|
+
return { apiUrl, token: null, machine: null, user: null, workspaces: [] };
|
|
29
73
|
}
|
|
30
74
|
}
|
|
31
75
|
|
|
32
76
|
function save(config) {
|
|
33
77
|
fs.mkdirSync(DIR, { recursive: true });
|
|
34
|
-
fs.writeFileSync(
|
|
78
|
+
fs.writeFileSync(fileFor(config.apiUrl), JSON.stringify(config, null, 2), { mode: 0o600 });
|
|
35
79
|
}
|
|
36
80
|
|
|
37
81
|
function samePath(a, b) {
|
|
@@ -43,4 +87,4 @@ function workspaceForPath(config, dir) {
|
|
|
43
87
|
return (config.workspaces || []).find((w) => samePath(w.path, dir)) || null;
|
|
44
88
|
}
|
|
45
89
|
|
|
46
|
-
module.exports = { DIR,
|
|
90
|
+
module.exports = { DIR, PRODUCTION_API, resolveApi, useApi, fileFor, load, save, samePath, workspaceForPath };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@motionode/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Connect your local repositories to Motionode: work tickets in Cursor, VS Code or any editor while Motionode keeps the work tied to the ticket, its git branches, verification and delivery.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"bin": {
|