nova-link-cli 1.0.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/bin/nova-link.js +2 -0
- package/package.json +20 -0
- package/src/activeProject.js +43 -0
- package/src/api.js +34 -0
- package/src/cli.js +30 -0
- package/src/commands/login.js +39 -0
- package/src/commands/pr.js +97 -0
- package/src/commands/projects.js +22 -0
- package/src/commands/pull.js +26 -0
- package/src/commands/push.js +41 -0
- package/src/commands/session.js +31 -0
- package/src/commands/signup.js +41 -0
- package/src/commands/use.js +25 -0
- package/src/config.js +25 -0
- package/src/prompt.js +13 -0
- package/src/repoUtils.js +30 -0
package/bin/nova-link.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "nova-link-cli",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Terminal companion for nova-link — log in, pick a project, and push/pull code or open pull requests, all from the command line.",
|
|
5
|
+
"bin": {
|
|
6
|
+
"nova-link": "./bin/nova-link.js"
|
|
7
|
+
},
|
|
8
|
+
"type": "commonjs",
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"keywords": ["nova-link", "cli", "push", "pull-request", "git-like"],
|
|
11
|
+
"engines": {
|
|
12
|
+
"node": ">=18"
|
|
13
|
+
},
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"yargs": "^17.7.2",
|
|
16
|
+
"chalk": "^4.1.2",
|
|
17
|
+
"fs-extra": "^11.2.0",
|
|
18
|
+
"tar": "^6.2.1"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
const chalk = require("chalk");
|
|
2
|
+
const { request } = require("./api");
|
|
3
|
+
const { loadConfig, saveConfig } = require("./config");
|
|
4
|
+
const { ask } = require("./prompt");
|
|
5
|
+
|
|
6
|
+
// Shown right after login/signup, and by `nova-link use`. Lets the person
|
|
7
|
+
// pick which project subsequent push/pull/pr commands should target.
|
|
8
|
+
async function chooseActiveProject() {
|
|
9
|
+
const { projects } = await request("/projects");
|
|
10
|
+
|
|
11
|
+
if (projects.length === 0) {
|
|
12
|
+
console.log(chalk.dim("You don't have any projects yet — ask a project owner to add you, then run `nova-link use <slug>`."));
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
console.log("\nYour projects:");
|
|
17
|
+
projects.forEach((p, i) => console.log(` ${i + 1}. ${p.name} ${chalk.dim(p.slug)}`));
|
|
18
|
+
|
|
19
|
+
const answer = await ask(`\nPick a project (1-${projects.length}): `);
|
|
20
|
+
const index = parseInt(answer, 10) - 1;
|
|
21
|
+
const chosen = projects[index];
|
|
22
|
+
|
|
23
|
+
if (!chosen) {
|
|
24
|
+
console.log(chalk.yellow("No project selected. Run `nova-link use <slug>` any time to set one."));
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const cfg = loadConfig();
|
|
29
|
+
saveConfig({ ...cfg, activeProjectId: chosen._id, activeProjectSlug: chosen.slug });
|
|
30
|
+
console.log(chalk.green(`Active project: ${chosen.slug}`));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Every push/pull/pr command needs this — fails loudly if nothing is active
|
|
34
|
+
// yet, rather than silently doing nothing.
|
|
35
|
+
function requireActiveProject() {
|
|
36
|
+
const cfg = loadConfig();
|
|
37
|
+
if (!cfg?.activeProjectId) {
|
|
38
|
+
throw new Error("No active project. Run `nova-link use <slug>` first (see `nova-link projects` for your options).");
|
|
39
|
+
}
|
|
40
|
+
return { id: cfg.activeProjectId, slug: cfg.activeProjectSlug };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
module.exports = { chooseActiveProject, requireActiveProject };
|
package/src/api.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
const { loadConfig } = require("./config");
|
|
2
|
+
|
|
3
|
+
function apiUrl() {
|
|
4
|
+
// Defaults to the deployed backend so `npm install -g nova-link-cli`
|
|
5
|
+
// works immediately for anyone, with no setup — override with
|
|
6
|
+
// NOVA_LINK_API_URL if you're pointing at a different instance (e.g. local dev).
|
|
7
|
+
return process.env.NOVA_LINK_API_URL || loadConfig()?.apiUrl || "https://backend-lmtu.onrender.com";
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
async function request(path, { method = "GET", body, auth = true } = {}) {
|
|
11
|
+
const headers = { "Content-Type": "application/json" };
|
|
12
|
+
|
|
13
|
+
if (auth) {
|
|
14
|
+
const cfg = loadConfig();
|
|
15
|
+
if (!cfg?.accessToken) {
|
|
16
|
+
throw new Error("Not logged in. Run `nova-link login` first.");
|
|
17
|
+
}
|
|
18
|
+
headers.Authorization = `Bearer ${cfg.accessToken}`;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const res = await fetch(`${apiUrl()}${path}`, {
|
|
22
|
+
method,
|
|
23
|
+
headers,
|
|
24
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const data = await res.json().catch(() => ({}));
|
|
28
|
+
if (!res.ok) {
|
|
29
|
+
throw new Error(data.error || `Request failed (${res.status})`);
|
|
30
|
+
}
|
|
31
|
+
return data;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
module.exports = { request, apiUrl };
|
package/src/cli.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
const yargs = require("yargs/yargs");
|
|
2
|
+
const { hideBin } = require("yargs/helpers");
|
|
3
|
+
|
|
4
|
+
const login = require("./commands/login");
|
|
5
|
+
const signup = require("./commands/signup");
|
|
6
|
+
const { logout, whoami } = require("./commands/session");
|
|
7
|
+
const projects = require("./commands/projects");
|
|
8
|
+
const use = require("./commands/use");
|
|
9
|
+
const push = require("./commands/push");
|
|
10
|
+
const pull = require("./commands/pull");
|
|
11
|
+
const pr = require("./commands/pr");
|
|
12
|
+
|
|
13
|
+
function run() {
|
|
14
|
+
yargs(hideBin(process.argv))
|
|
15
|
+
.scriptName("nova-link")
|
|
16
|
+
.command(login)
|
|
17
|
+
.command(signup)
|
|
18
|
+
.command(logout)
|
|
19
|
+
.command(whoami)
|
|
20
|
+
.command(projects)
|
|
21
|
+
.command(use)
|
|
22
|
+
.command(push)
|
|
23
|
+
.command(pull)
|
|
24
|
+
.command(pr)
|
|
25
|
+
.demandCommand(1, "Run `nova-link --help` to see available commands.")
|
|
26
|
+
.strict()
|
|
27
|
+
.help().argv;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
module.exports = { run };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
const chalk = require("chalk");
|
|
2
|
+
const { request, apiUrl } = require("../api");
|
|
3
|
+
const { saveConfig } = require("../config");
|
|
4
|
+
const { ask } = require("../prompt");
|
|
5
|
+
const { chooseActiveProject } = require("../activeProject");
|
|
6
|
+
|
|
7
|
+
module.exports = {
|
|
8
|
+
command: "login",
|
|
9
|
+
describe: "Log in and pick which project you're working on",
|
|
10
|
+
builder: (yargs) =>
|
|
11
|
+
yargs
|
|
12
|
+
.option("username", { alias: "u", type: "string", describe: "Email or username" })
|
|
13
|
+
.option("password", { alias: "p", type: "string", describe: "Password" }),
|
|
14
|
+
handler: async (argv) => {
|
|
15
|
+
try {
|
|
16
|
+
const username = argv.username || (await ask("Email or username: "));
|
|
17
|
+
const password = argv.password || (await ask("Password: "));
|
|
18
|
+
|
|
19
|
+
const data = await request("/auth/cli-login", {
|
|
20
|
+
method: "POST",
|
|
21
|
+
auth: false,
|
|
22
|
+
body: { emailOrUsername: username, password },
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
saveConfig({
|
|
26
|
+
accessToken: data.accessToken,
|
|
27
|
+
refreshToken: data.refreshToken,
|
|
28
|
+
username: data.username,
|
|
29
|
+
apiUrl: apiUrl(),
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
console.log(chalk.green(`Logged in as ${data.username}.`));
|
|
33
|
+
await chooseActiveProject();
|
|
34
|
+
} catch (err) {
|
|
35
|
+
console.error(chalk.red(err.message));
|
|
36
|
+
process.exitCode = 1;
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
};
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
const chalk = require("chalk");
|
|
2
|
+
const { request } = require("../api");
|
|
3
|
+
const { requireActiveProject } = require("../activeProject");
|
|
4
|
+
const { packDirectory } = require("../repoUtils");
|
|
5
|
+
|
|
6
|
+
async function create(argv) {
|
|
7
|
+
try {
|
|
8
|
+
const project = requireActiveProject();
|
|
9
|
+
|
|
10
|
+
console.log(chalk.dim(`Checking permission on ${project.slug} and packing directory...`));
|
|
11
|
+
const [{ uploadUrl, archiveKey }, archive] = await Promise.all([
|
|
12
|
+
request(`/projects/${project.id}/pull-requests/init`, { method: "POST" }),
|
|
13
|
+
packDirectory(),
|
|
14
|
+
]);
|
|
15
|
+
|
|
16
|
+
console.log(chalk.dim("Uploading to S3..."));
|
|
17
|
+
const uploadRes = await fetch(uploadUrl, {
|
|
18
|
+
method: "PUT",
|
|
19
|
+
headers: { "Content-Type": "application/gzip" },
|
|
20
|
+
body: archive,
|
|
21
|
+
});
|
|
22
|
+
if (!uploadRes.ok) {
|
|
23
|
+
throw new Error(`Upload to S3 failed (${uploadRes.status}). Check the backend's AWS credentials and bucket permissions.`);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const { pullRequest } = await request(`/projects/${project.id}/pull-requests/finalize`, {
|
|
27
|
+
method: "POST",
|
|
28
|
+
body: { message: argv.message, archiveKey },
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
console.log(chalk.green(`Pull request opened on ${project.slug}: ${pullRequest._id}`));
|
|
32
|
+
console.log(chalk.dim("A maintainer can accept it with `nova-link pr accept <id>`."));
|
|
33
|
+
} catch (err) {
|
|
34
|
+
console.error(chalk.red(err.message));
|
|
35
|
+
process.exitCode = 1;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function list() {
|
|
40
|
+
try {
|
|
41
|
+
const project = requireActiveProject();
|
|
42
|
+
const { pullRequests } = await request(`/projects/${project.id}/pull-requests`);
|
|
43
|
+
|
|
44
|
+
if (pullRequests.length === 0) {
|
|
45
|
+
console.log(chalk.dim(`No pull requests on ${project.slug}.`));
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
pullRequests.forEach((pr) => {
|
|
49
|
+
const statusColor = pr.status === "OPEN" ? chalk.yellow : pr.status === "MERGED" ? chalk.green : chalk.dim;
|
|
50
|
+
console.log(`${chalk.bold(pr._id)} ${statusColor(pr.status)} ${chalk.dim(pr.author?.username)} ${pr.message}`);
|
|
51
|
+
});
|
|
52
|
+
} catch (err) {
|
|
53
|
+
console.error(chalk.red(err.message));
|
|
54
|
+
process.exitCode = 1;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function accept(argv) {
|
|
59
|
+
try {
|
|
60
|
+
const project = requireActiveProject();
|
|
61
|
+
const { commit } = await request(`/projects/${project.id}/pull-requests/${argv.id}/accept`, { method: "POST" });
|
|
62
|
+
console.log(chalk.green(`Merged into ${project.slug}. New commit: ${commit._id}`));
|
|
63
|
+
console.log(chalk.dim("Run `nova-link pull` to get the merged code."));
|
|
64
|
+
} catch (err) {
|
|
65
|
+
console.error(chalk.red(err.message));
|
|
66
|
+
process.exitCode = 1;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async function reject(argv) {
|
|
71
|
+
try {
|
|
72
|
+
const project = requireActiveProject();
|
|
73
|
+
await request(`/projects/${project.id}/pull-requests/${argv.id}/reject`, { method: "POST" });
|
|
74
|
+
console.log(chalk.yellow("Pull request rejected."));
|
|
75
|
+
} catch (err) {
|
|
76
|
+
console.error(chalk.red(err.message));
|
|
77
|
+
process.exitCode = 1;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
module.exports = {
|
|
82
|
+
command: "pr",
|
|
83
|
+
describe: "Manage pull requests on your active project",
|
|
84
|
+
builder: (yargs) =>
|
|
85
|
+
yargs
|
|
86
|
+
.command({
|
|
87
|
+
command: "create",
|
|
88
|
+
describe: "Open a pull request (contributor+)",
|
|
89
|
+
builder: (y) => y.option("message", { alias: "m", type: "string", default: "Update" }),
|
|
90
|
+
handler: create,
|
|
91
|
+
})
|
|
92
|
+
.command({ command: "list", describe: "List pull requests", handler: list })
|
|
93
|
+
.command({ command: "accept <id>", describe: "Accept and merge (maintainer/owner)", handler: accept })
|
|
94
|
+
.command({ command: "reject <id>", describe: "Reject (maintainer/owner)", handler: reject })
|
|
95
|
+
.demandCommand(1, "Specify a pr subcommand: create, list, accept, or reject."),
|
|
96
|
+
handler: () => {},
|
|
97
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
const chalk = require("chalk");
|
|
2
|
+
const { request } = require("../api");
|
|
3
|
+
|
|
4
|
+
module.exports = {
|
|
5
|
+
command: "projects",
|
|
6
|
+
describe: "List your projects",
|
|
7
|
+
handler: async () => {
|
|
8
|
+
try {
|
|
9
|
+
const { projects } = await request("/projects");
|
|
10
|
+
if (projects.length === 0) {
|
|
11
|
+
console.log(chalk.dim("No projects yet — create one from the web app."));
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
projects.forEach((p) => {
|
|
15
|
+
console.log(`${chalk.bold(p.name)} ${chalk.dim(p.slug)} ${chalk.dim(p.visibility)}`);
|
|
16
|
+
});
|
|
17
|
+
} catch (err) {
|
|
18
|
+
console.error(chalk.red(err.message));
|
|
19
|
+
process.exitCode = 1;
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
const chalk = require("chalk");
|
|
2
|
+
const { request } = require("../api");
|
|
3
|
+
const { requireActiveProject } = require("../activeProject");
|
|
4
|
+
const { unpackArchive } = require("../repoUtils");
|
|
5
|
+
|
|
6
|
+
module.exports = {
|
|
7
|
+
command: "pull",
|
|
8
|
+
describe: "Fetch your active project's latest push into the current directory (overwrites local files)",
|
|
9
|
+
handler: async () => {
|
|
10
|
+
try {
|
|
11
|
+
const project = requireActiveProject();
|
|
12
|
+
const { commit } = await request(`/projects/${project.id}/commits/latest`);
|
|
13
|
+
|
|
14
|
+
console.log(chalk.dim("Downloading from S3..."));
|
|
15
|
+
const downloadRes = await fetch(commit.downloadUrl);
|
|
16
|
+
if (!downloadRes.ok) throw new Error(`Download from S3 failed (${downloadRes.status}).`);
|
|
17
|
+
const buffer = Buffer.from(await downloadRes.arrayBuffer());
|
|
18
|
+
|
|
19
|
+
await unpackArchive(buffer, process.cwd());
|
|
20
|
+
console.log(chalk.green(`Pulled ${project.slug} commit ${commit._id}: ${commit.message}`));
|
|
21
|
+
} catch (err) {
|
|
22
|
+
console.error(chalk.red(err.message));
|
|
23
|
+
process.exitCode = 1;
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
const chalk = require("chalk");
|
|
2
|
+
const { request } = require("../api");
|
|
3
|
+
const { requireActiveProject } = require("../activeProject");
|
|
4
|
+
const { packDirectory } = require("../repoUtils");
|
|
5
|
+
|
|
6
|
+
module.exports = {
|
|
7
|
+
command: "push",
|
|
8
|
+
describe: "Push the current directory to your active project (maintainer/owner only — contributors use `nova-link pr create`)",
|
|
9
|
+
builder: (yargs) => yargs.option("message", { alias: "m", type: "string", default: "Update" }),
|
|
10
|
+
handler: async (argv) => {
|
|
11
|
+
try {
|
|
12
|
+
const project = requireActiveProject();
|
|
13
|
+
|
|
14
|
+
console.log(chalk.dim(`Checking permission on ${project.slug} and packing directory...`));
|
|
15
|
+
const [{ uploadUrl, archiveKey }, archive] = await Promise.all([
|
|
16
|
+
request(`/projects/${project.id}/commits/init`, { method: "POST" }),
|
|
17
|
+
packDirectory(),
|
|
18
|
+
]);
|
|
19
|
+
|
|
20
|
+
console.log(chalk.dim("Uploading to S3..."));
|
|
21
|
+
const uploadRes = await fetch(uploadUrl, {
|
|
22
|
+
method: "PUT",
|
|
23
|
+
headers: { "Content-Type": "application/gzip" },
|
|
24
|
+
body: archive,
|
|
25
|
+
});
|
|
26
|
+
if (!uploadRes.ok) {
|
|
27
|
+
throw new Error(`Upload to S3 failed (${uploadRes.status}). Check the backend's AWS credentials and bucket permissions.`);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const { commit } = await request(`/projects/${project.id}/commits/finalize`, {
|
|
31
|
+
method: "POST",
|
|
32
|
+
body: { message: argv.message, archiveKey },
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
console.log(chalk.green(`Pushed to ${project.slug}. New commit: ${commit._id}`));
|
|
36
|
+
} catch (err) {
|
|
37
|
+
console.error(chalk.red(err.message));
|
|
38
|
+
process.exitCode = 1;
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
const chalk = require("chalk");
|
|
2
|
+
const { loadConfig, clearConfig } = require("../config");
|
|
3
|
+
|
|
4
|
+
const logout = {
|
|
5
|
+
command: "logout",
|
|
6
|
+
describe: "Forget your saved login on this machine",
|
|
7
|
+
handler: () => {
|
|
8
|
+
clearConfig();
|
|
9
|
+
console.log(chalk.green("Logged out."));
|
|
10
|
+
},
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const whoami = {
|
|
14
|
+
command: "whoami",
|
|
15
|
+
describe: "Show who you're logged in as and your active project",
|
|
16
|
+
handler: () => {
|
|
17
|
+
const cfg = loadConfig();
|
|
18
|
+
if (!cfg?.username) {
|
|
19
|
+
console.log(chalk.yellow("Not logged in. Run `nova-link login`."));
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
console.log(`${chalk.bold(cfg.username)} ${chalk.dim(cfg.apiUrl)}`);
|
|
23
|
+
console.log(
|
|
24
|
+
cfg.activeProjectSlug
|
|
25
|
+
? `Active project: ${chalk.bold(cfg.activeProjectSlug)}`
|
|
26
|
+
: chalk.dim("No active project — run `nova-link use <slug>`.")
|
|
27
|
+
);
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
module.exports = { logout, whoami };
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
const chalk = require("chalk");
|
|
2
|
+
const { request, apiUrl } = require("../api");
|
|
3
|
+
const { saveConfig } = require("../config");
|
|
4
|
+
const { ask } = require("../prompt");
|
|
5
|
+
const { chooseActiveProject } = require("../activeProject");
|
|
6
|
+
|
|
7
|
+
module.exports = {
|
|
8
|
+
command: "signup",
|
|
9
|
+
describe: "Create a new account from the terminal",
|
|
10
|
+
builder: (yargs) =>
|
|
11
|
+
yargs
|
|
12
|
+
.option("username", { alias: "u", type: "string" })
|
|
13
|
+
.option("email", { alias: "e", type: "string" })
|
|
14
|
+
.option("password", { alias: "p", type: "string" }),
|
|
15
|
+
handler: async (argv) => {
|
|
16
|
+
try {
|
|
17
|
+
const username = argv.username || (await ask("Username: "));
|
|
18
|
+
const email = argv.email || (await ask("Email: "));
|
|
19
|
+
const password = argv.password || (await ask("Password: "));
|
|
20
|
+
|
|
21
|
+
const data = await request("/auth/signup", {
|
|
22
|
+
method: "POST",
|
|
23
|
+
auth: false,
|
|
24
|
+
body: { username, email, password },
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
saveConfig({
|
|
28
|
+
accessToken: data.accessToken,
|
|
29
|
+
refreshToken: data.refreshToken,
|
|
30
|
+
username: data.user.username,
|
|
31
|
+
apiUrl: apiUrl(),
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
console.log(chalk.green(`Account created. Logged in as ${data.user.username}.`));
|
|
35
|
+
await chooseActiveProject();
|
|
36
|
+
} catch (err) {
|
|
37
|
+
console.error(chalk.red(err.message));
|
|
38
|
+
process.exitCode = 1;
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
const chalk = require("chalk");
|
|
2
|
+
const { request } = require("../api");
|
|
3
|
+
const { loadConfig, saveConfig } = require("../config");
|
|
4
|
+
|
|
5
|
+
module.exports = {
|
|
6
|
+
command: "use <projectSlug>",
|
|
7
|
+
describe: "Switch which project push/pull/pr act on",
|
|
8
|
+
builder: (yargs) => yargs.positional("projectSlug", { type: "string" }),
|
|
9
|
+
handler: async (argv) => {
|
|
10
|
+
try {
|
|
11
|
+
const { projects } = await request("/projects");
|
|
12
|
+
const project = projects.find((p) => p.slug === argv.projectSlug);
|
|
13
|
+
if (!project) {
|
|
14
|
+
throw new Error(`No project found with slug "${argv.projectSlug}". Run \`nova-link projects\` to see your options.`);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const cfg = loadConfig();
|
|
18
|
+
saveConfig({ ...cfg, activeProjectId: project._id, activeProjectSlug: project.slug });
|
|
19
|
+
console.log(chalk.green(`Active project: ${project.slug}`));
|
|
20
|
+
} catch (err) {
|
|
21
|
+
console.error(chalk.red(err.message));
|
|
22
|
+
process.exitCode = 1;
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
};
|
package/src/config.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
const os = require("os");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const fs = require("fs-extra");
|
|
4
|
+
|
|
5
|
+
const CONFIG_DIR = path.join(os.homedir(), ".nova-link");
|
|
6
|
+
const CONFIG_PATH = path.join(CONFIG_DIR, "config.json");
|
|
7
|
+
|
|
8
|
+
function loadConfig() {
|
|
9
|
+
try {
|
|
10
|
+
return fs.readJsonSync(CONFIG_PATH);
|
|
11
|
+
} catch (err) {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function saveConfig(data) {
|
|
17
|
+
fs.ensureDirSync(CONFIG_DIR);
|
|
18
|
+
fs.writeJsonSync(CONFIG_PATH, data, { spaces: 2 });
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function clearConfig() {
|
|
22
|
+
fs.removeSync(CONFIG_PATH);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
module.exports = { loadConfig, saveConfig, clearConfig, CONFIG_PATH };
|
package/src/prompt.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
const readline = require("readline");
|
|
2
|
+
|
|
3
|
+
function ask(question) {
|
|
4
|
+
return new Promise((resolve) => {
|
|
5
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
6
|
+
rl.question(question, (answer) => {
|
|
7
|
+
rl.close();
|
|
8
|
+
resolve(answer);
|
|
9
|
+
});
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
module.exports = { ask };
|
package/src/repoUtils.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
const fs = require("fs-extra");
|
|
2
|
+
const tar = require("tar");
|
|
3
|
+
const { Readable } = require("stream");
|
|
4
|
+
|
|
5
|
+
const EXCLUDES = new Set(["node_modules", ".git", ".nova-link"]);
|
|
6
|
+
|
|
7
|
+
// Gzipped tarball of everything in `dir` except the excluded set above.
|
|
8
|
+
async function packDirectory(dir = process.cwd()) {
|
|
9
|
+
const entries = fs.readdirSync(dir).filter((name) => !EXCLUDES.has(name));
|
|
10
|
+
const chunks = [];
|
|
11
|
+
await new Promise((resolve, reject) => {
|
|
12
|
+
const stream = tar.create({ gzip: true, cwd: dir }, entries);
|
|
13
|
+
stream.on("data", (chunk) => chunks.push(chunk));
|
|
14
|
+
stream.on("end", resolve);
|
|
15
|
+
stream.on("error", reject);
|
|
16
|
+
});
|
|
17
|
+
return Buffer.concat(chunks);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async function unpackArchive(buffer, destDir) {
|
|
21
|
+
fs.ensureDirSync(destDir);
|
|
22
|
+
await new Promise((resolve, reject) => {
|
|
23
|
+
Readable.from(buffer)
|
|
24
|
+
.pipe(tar.extract({ cwd: destDir }))
|
|
25
|
+
.on("finish", resolve)
|
|
26
|
+
.on("error", reject);
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
module.exports = { packDirectory, unpackArchive };
|