runcloud 0.2.178 → 0.2.180
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/commands/domains.js +13 -4
- package/dist/commands/email.js +261 -11
- package/dist/commands/github.js +61 -0
- package/dist/index.js +2 -0
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/runcloud-0.2.180.tgz +0 -0
- package/runcloud-0.2.178.tgz +0 -0
package/dist/commands/domains.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { stdin as input, stdout as output } from 'node:process';
|
|
2
2
|
import { createInterface } from 'node:readline/promises';
|
|
3
|
-
import { ApiClient, friendlyApiError } from '../api.js';
|
|
3
|
+
import { ApiClient, ApiError, friendlyApiError } from '../api.js';
|
|
4
4
|
import { requireCredentials, resolveProject } from '../config.js';
|
|
5
5
|
export function renderPendingRegistration(order) {
|
|
6
6
|
const action = `run \`newly domains connect-registered ${order.domain}\``;
|
|
@@ -27,9 +27,9 @@ export function summarizeDomain(candidate) {
|
|
|
27
27
|
return `${name} ${state}${pricing}${reason}`;
|
|
28
28
|
}
|
|
29
29
|
export function renderProjectDomain(domain) {
|
|
30
|
-
return `${domain.id} ${domain.hostname} ${domain.status} ${domain.ownership_mode}`;
|
|
30
|
+
return `${domain.id} ${domain.hostname} ${domain.status} ${domain.ownership_mode} email: ${domain.email_status ?? 'not set up'}`;
|
|
31
31
|
}
|
|
32
|
-
async function confirm(prompt) {
|
|
32
|
+
export async function confirm(prompt) {
|
|
33
33
|
const rl = createInterface({ input, output });
|
|
34
34
|
try {
|
|
35
35
|
const answer = (await rl.question(`${prompt} Type "yes" to continue: `)).trim().toLowerCase();
|
|
@@ -116,7 +116,16 @@ export async function runDomainStatus(client, projectId, hostnameOrId, log = con
|
|
|
116
116
|
}
|
|
117
117
|
if (!domain)
|
|
118
118
|
throw new Error('Domain not found.');
|
|
119
|
-
|
|
119
|
+
let refreshed;
|
|
120
|
+
try {
|
|
121
|
+
refreshed = await client.post(`/projects/${projectId}/domains/${domain.id}/refresh`, {});
|
|
122
|
+
}
|
|
123
|
+
catch (e) {
|
|
124
|
+
if (!(e instanceof ApiError && e.status === 403))
|
|
125
|
+
throw e;
|
|
126
|
+
log(renderProjectDomain(domain));
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
120
129
|
log(renderProjectDomain(refreshed.domain));
|
|
121
130
|
if (refreshed.dns_instructions)
|
|
122
131
|
log(JSON.stringify(refreshed.dns_instructions, null, 2));
|
package/dist/commands/email.js
CHANGED
|
@@ -1,13 +1,263 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import { readFileSync, mkdtempSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { tmpdir } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { ApiClient, ApiError, friendlyApiError } from '../api.js';
|
|
5
|
+
import { requireCredentials, resolveProject } from '../config.js';
|
|
6
|
+
import { confirm } from './domains.js';
|
|
7
|
+
export const ASK_USER_TO_SEND = 'Ask the user to review and send it in Newly';
|
|
8
|
+
export const NEEDS_TERMINAL = 'Sending needs you at a terminal. Review and send it in Newly instead.';
|
|
9
|
+
const stdinIsTty = () => Boolean(process.stdin.isTTY);
|
|
10
|
+
const base = (projectId) => `/projects/${projectId}/email`;
|
|
11
|
+
const show = (log, value) => log(JSON.stringify(value, null, 2));
|
|
12
|
+
const campaignLine = (c) => `${c.id} ${c.status} ${c.name}`;
|
|
13
|
+
export async function runEmailSettings(client, projectId, opts, log = console.log) {
|
|
14
|
+
const body = {
|
|
15
|
+
...(opts.senderName !== undefined && { sender_name: opts.senderName }),
|
|
16
|
+
...(opts.postalAddress !== undefined && { postal_address: opts.postalAddress }),
|
|
17
|
+
...(opts.replyTo !== undefined && { reply_to: opts.replyTo }),
|
|
18
|
+
};
|
|
19
|
+
show(log, Object.keys(body).length
|
|
20
|
+
? await client.put(`${base(projectId)}/settings`, body)
|
|
21
|
+
: await client.get(`${base(projectId)}/settings`));
|
|
22
|
+
}
|
|
23
|
+
export async function runContactsCount(client, projectId, log = console.log) {
|
|
24
|
+
show(log, await client.get(`${base(projectId)}/contacts/counts`));
|
|
25
|
+
}
|
|
26
|
+
export async function runContactsList(client, projectId, opts, log = console.log) {
|
|
27
|
+
const params = new URLSearchParams();
|
|
28
|
+
if (opts.status)
|
|
29
|
+
params.set('status', opts.status);
|
|
30
|
+
if (opts.q)
|
|
31
|
+
params.set('q', opts.q);
|
|
32
|
+
const query = params.toString();
|
|
33
|
+
show(log, await client.get(`${base(projectId)}/contacts${query ? `?${query}` : ''}`));
|
|
34
|
+
}
|
|
35
|
+
export async function runCampaignList(client, projectId, log = console.log) {
|
|
36
|
+
const { campaigns = [] } = (await client.get(`${base(projectId)}/campaigns`));
|
|
37
|
+
log(campaigns.length ? campaigns.map(campaignLine).join('\n') : 'No campaigns.');
|
|
38
|
+
}
|
|
39
|
+
function readHtml(path) {
|
|
40
|
+
try {
|
|
41
|
+
return readFileSync(path, 'utf8');
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
throw new Error(`Could not read ${path}`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
export async function runCampaignDraft(client, projectId, opts, log = console.log) {
|
|
48
|
+
const body = {
|
|
49
|
+
...(opts.name !== undefined && { name: opts.name }),
|
|
50
|
+
...(opts.subject !== undefined && { subject: opts.subject }),
|
|
51
|
+
...(opts.previewText !== undefined && { preview_text: opts.previewText }),
|
|
52
|
+
...(opts.topic !== undefined && { topic_key: opts.topic }),
|
|
53
|
+
...(opts.htmlFile !== undefined && { html: readHtml(opts.htmlFile) }),
|
|
54
|
+
};
|
|
55
|
+
if (!opts.id && !opts.name)
|
|
56
|
+
throw new Error('A new campaign needs --name.');
|
|
57
|
+
const saved = (opts.id
|
|
58
|
+
? await client.patch(`${base(projectId)}/campaigns/${opts.id}`, body)
|
|
59
|
+
: await client.post(`${base(projectId)}/campaigns`, body));
|
|
60
|
+
log(campaignLine(saved));
|
|
61
|
+
}
|
|
62
|
+
export async function runCampaignPreview(client, projectId, id, log = console.log) {
|
|
63
|
+
const campaign = (await client.get(`${base(projectId)}/campaigns/${id}`));
|
|
64
|
+
const file = join(mkdtempSync(join(tmpdir(), 'newly-campaign-')), 'preview.html');
|
|
65
|
+
writeFileSync(file, campaign.html);
|
|
66
|
+
log(file);
|
|
67
|
+
}
|
|
68
|
+
export async function runCampaignTest(client, projectId, id, log = console.log) {
|
|
69
|
+
await client.post(`${base(projectId)}/campaigns/${id}/test`, {});
|
|
70
|
+
log('Test email sent to your account address.');
|
|
71
|
+
}
|
|
72
|
+
export async function runCampaignStatus(client, projectId, id, log = console.log) {
|
|
73
|
+
const campaign = (await client.get(`${base(projectId)}/campaigns/${id}`));
|
|
74
|
+
const estimate = (await client.get(`${base(projectId)}/campaigns/${id}/estimate`));
|
|
75
|
+
log(campaignLine(campaign));
|
|
76
|
+
show(log, { estimate });
|
|
77
|
+
}
|
|
78
|
+
async function confirmedPost(client, projectId, id, action, extra, verb, log, confirmFn, isTty) {
|
|
79
|
+
if (!isTty())
|
|
80
|
+
throw new Error(NEEDS_TERMINAL);
|
|
81
|
+
const campaign = (await client.get(`${base(projectId)}/campaigns/${id}`));
|
|
82
|
+
const estimate = (await client.get(`${base(projectId)}/campaigns/${id}/estimate`));
|
|
83
|
+
log(`${campaign.name}: ${estimate.recipients} recipients, ${estimate.credits} credits.`);
|
|
84
|
+
if (!(await confirmFn(`${verb} "${campaign.name}"?`))) {
|
|
85
|
+
log('Cancelled.');
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
const result = (await client.post(`${base(projectId)}/campaigns/${id}/${action}`, {
|
|
89
|
+
...extra,
|
|
90
|
+
expected_updated_at: campaign.updated_at,
|
|
91
|
+
}));
|
|
92
|
+
log(campaignLine(result));
|
|
93
|
+
}
|
|
94
|
+
export const runCampaignSend = (client, projectId, id, log = console.log, confirmFn = confirm, isTty = stdinIsTty) => confirmedPost(client, projectId, id, 'send', {}, 'Send', log, confirmFn, isTty);
|
|
95
|
+
export const runCampaignSchedule = (client, projectId, id, opts, log = console.log, confirmFn = confirm, isTty = stdinIsTty) => confirmedPost(client, projectId, id, 'schedule', { at: opts.at, timezone: opts.timezone ?? Intl.DateTimeFormat().resolvedOptions().timeZone }, `Schedule for ${opts.at}`, log, confirmFn, isTty);
|
|
96
|
+
export const USER_SESSION_ONLY = 'This needs a signed-in user session. Ask the user to do it in Newly.';
|
|
97
|
+
const PENDING_NOTE = 'Newly checks every minute; this usually takes 5 to 30 minutes after the records are added.';
|
|
98
|
+
const relativeName = (name, root) => name === root ? '@' : name.endsWith(`.${root}`) ? name.slice(0, -root.length - 1) : name;
|
|
99
|
+
function recordLine(r, root) {
|
|
100
|
+
const content = r.type === 'TXT' ? r.content.replace(/^"(.*)"$/, '$1') : r.content;
|
|
101
|
+
const value = r.priority === undefined ? content : `${r.priority} ${content}`;
|
|
102
|
+
const note = r.optional ? ' (optional, you already have one)' : '';
|
|
103
|
+
return ` ${r.type} ${r.name} (relative: ${relativeName(r.name, root)}) ${value}${note}`;
|
|
104
|
+
}
|
|
105
|
+
function domainLines(d) {
|
|
106
|
+
const status = d.status ?? 'pending';
|
|
107
|
+
const head = `${d.hostname} ${d.kind} ${status}`;
|
|
108
|
+
if (d.kind !== 'own' || status === 'verified')
|
|
109
|
+
return [head];
|
|
110
|
+
return [
|
|
111
|
+
head,
|
|
112
|
+
...(d.reason ? [` ${d.reason.replace(/ Press Check now.*/i, '')}`] : []),
|
|
113
|
+
...(d.records ?? []).map((r) => recordLine(r, d.root_domain)),
|
|
114
|
+
];
|
|
115
|
+
}
|
|
116
|
+
const RECORDS_POLL_MS = 3_000;
|
|
117
|
+
const RECORDS_POLL_TRIES = 10;
|
|
118
|
+
const realSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
119
|
+
async function waitForRecords(client, projectId, claim, sleep) {
|
|
120
|
+
let d = claim;
|
|
121
|
+
for (let i = 0; i < RECORDS_POLL_TRIES && !d.records && d.status !== 'verified' && d.status !== 'failed'; i++) {
|
|
122
|
+
await sleep(RECORDS_POLL_MS);
|
|
123
|
+
const { domains = [] } = (await client.get(`${base(projectId)}/domains`));
|
|
124
|
+
d = domains.find((x) => x.id === claim.id) ?? d;
|
|
125
|
+
}
|
|
126
|
+
return d;
|
|
127
|
+
}
|
|
128
|
+
export async function runEmailDomainAdd(client, projectId, hostname, log = console.log, sleep = realSleep) {
|
|
129
|
+
const claim = (await client.post(`${base(projectId)}/domains`, { hostname }));
|
|
130
|
+
if (claim.kind === 'managed') {
|
|
131
|
+
log(`Newly manages DNS for ${claim.hostname}, so email is set up automatically. There is nothing to add.`);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
if (claim.status === 'verified') {
|
|
135
|
+
log(`${claim.hostname} is verified. Email sends from it.`);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
if (!claim.records)
|
|
139
|
+
log(`Preparing the DNS records for ${claim.hostname}...`);
|
|
140
|
+
const d = await waitForRecords(client, projectId, claim, sleep);
|
|
141
|
+
if (d.status === 'failed' && d.reason)
|
|
142
|
+
log(d.reason.replace(/ Press Check now.*/i, ''));
|
|
143
|
+
if (!d.records) {
|
|
144
|
+
log('The records are not ready yet. Run "newly email domain status" in a minute to see them.');
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
log(`Add these DNS records at your DNS provider for ${d.hostname}:`);
|
|
148
|
+
log(d.records.map((r) => recordLine(r, d.root_domain)).join('\n'));
|
|
149
|
+
log(`Status: ${d.status ?? 'pending'}. ${PENDING_NOTE}`);
|
|
150
|
+
}
|
|
151
|
+
export async function runEmailDomainStatus(client, projectId, log = console.log) {
|
|
152
|
+
const { domains = [] } = (await client.get(`${base(projectId)}/domains`));
|
|
153
|
+
log(domains.length ? domains.flatMap(domainLines).join('\n') : 'No email domains.');
|
|
154
|
+
}
|
|
155
|
+
export function emailDomainErrorMessage(e) {
|
|
156
|
+
return e instanceof ApiError && e.status === 403 ? USER_SESSION_ONLY : friendlyApiError(e);
|
|
157
|
+
}
|
|
158
|
+
export function emailErrorMessage(e, isSend = false) {
|
|
159
|
+
return isSend && e instanceof ApiError && e.status === 403 ? ASK_USER_TO_SEND : friendlyApiError(e);
|
|
160
|
+
}
|
|
3
161
|
export function registerEmail(program) {
|
|
4
|
-
program
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
162
|
+
const email = program.command('email').description('Marketing email: settings, contacts and campaigns');
|
|
163
|
+
const run = (fn, isSend = false) => async (opts) => {
|
|
164
|
+
try {
|
|
165
|
+
const creds = requireCredentials();
|
|
166
|
+
await fn(new ApiClient(creds.apiUrl, creds.token), resolveProject(process.cwd(), opts.project));
|
|
167
|
+
}
|
|
168
|
+
catch (e) {
|
|
169
|
+
console.error(emailErrorMessage(e, isSend));
|
|
170
|
+
process.exitCode = 1;
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
const project = '--project <id>';
|
|
174
|
+
const projectHelp = 'project id (defaults to the linked directory)';
|
|
175
|
+
email
|
|
176
|
+
.command('settings')
|
|
177
|
+
.option(project, projectHelp)
|
|
178
|
+
.option('--sender-name <name>')
|
|
179
|
+
.option('--postal-address <address>')
|
|
180
|
+
.option('--reply-to <email>')
|
|
181
|
+
.description('Show email settings, or update them when a flag is given')
|
|
182
|
+
.action((opts) => run((c, p) => runEmailSettings(c, p, opts))(opts));
|
|
183
|
+
const contacts = email.command('contacts').description('Marketing contacts');
|
|
184
|
+
contacts
|
|
185
|
+
.command('count')
|
|
186
|
+
.option(project, projectHelp)
|
|
187
|
+
.action((opts) => run((c, p) => runContactsCount(c, p))(opts));
|
|
188
|
+
contacts
|
|
189
|
+
.command('list')
|
|
190
|
+
.option(project, projectHelp)
|
|
191
|
+
.option('--status <status>')
|
|
192
|
+
.option('--q <text>', 'search by email')
|
|
193
|
+
.action((opts) => run((c, p) => runContactsList(c, p, opts))(opts));
|
|
194
|
+
const campaign = email.command('campaign').description('Draft, preview, test and send campaigns');
|
|
195
|
+
campaign
|
|
196
|
+
.command('list')
|
|
197
|
+
.option(project, projectHelp)
|
|
198
|
+
.action((opts) => run((c, p) => runCampaignList(c, p))(opts));
|
|
199
|
+
campaign
|
|
200
|
+
.command('draft')
|
|
201
|
+
.option(project, projectHelp)
|
|
202
|
+
.option('--id <id>', 'edit this draft instead of creating one')
|
|
203
|
+
.option('--name <name>')
|
|
204
|
+
.option('--subject <subject>')
|
|
205
|
+
.option('--preview-text <text>')
|
|
206
|
+
.option('--topic <key>')
|
|
207
|
+
.option('--html-file <path>')
|
|
208
|
+
.description('Create a draft campaign, or update one with --id')
|
|
209
|
+
.action((opts) => run((c, p) => runCampaignDraft(c, p, opts))(opts));
|
|
210
|
+
campaign
|
|
211
|
+
.command('preview')
|
|
212
|
+
.argument('<id>')
|
|
213
|
+
.option(project, projectHelp)
|
|
214
|
+
.description('Write the campaign HTML to a temp file and print its path')
|
|
215
|
+
.action((id, opts) => run((c, p) => runCampaignPreview(c, p, id))(opts));
|
|
216
|
+
campaign
|
|
217
|
+
.command('test')
|
|
218
|
+
.argument('<id>')
|
|
219
|
+
.option(project, projectHelp)
|
|
220
|
+
.description('Send a test email to your own account address')
|
|
221
|
+
.action((id, opts) => run((c, p) => runCampaignTest(c, p, id))(opts));
|
|
222
|
+
campaign
|
|
223
|
+
.command('status')
|
|
224
|
+
.argument('<id>')
|
|
225
|
+
.option(project, projectHelp)
|
|
226
|
+
.action((id, opts) => run((c, p) => runCampaignStatus(c, p, id))(opts));
|
|
227
|
+
campaign
|
|
228
|
+
.command('send')
|
|
229
|
+
.argument('<id>')
|
|
230
|
+
.option(project, projectHelp)
|
|
231
|
+
.description('Send a campaign now (needs a terminal and confirmation)')
|
|
232
|
+
.action((id, opts) => run((c, p) => runCampaignSend(c, p, id), true)(opts));
|
|
233
|
+
campaign
|
|
234
|
+
.command('schedule')
|
|
235
|
+
.argument('<id>')
|
|
236
|
+
.requiredOption('--at <iso>', 'ISO date and time')
|
|
237
|
+
.option('--timezone <iana>', 'IANA timezone (defaults to this machine)')
|
|
238
|
+
.option(project, projectHelp)
|
|
239
|
+
.description('Schedule a campaign (needs a terminal and confirmation)')
|
|
240
|
+
.action((id, opts) => run((c, p) => runCampaignSchedule(c, p, id, opts), true)(opts));
|
|
241
|
+
const domain = email.command('domain').description('Send email from a domain you own');
|
|
242
|
+
const runDomain = (fn) => async (opts) => {
|
|
243
|
+
try {
|
|
244
|
+
const creds = requireCredentials();
|
|
245
|
+
await fn(new ApiClient(creds.apiUrl, creds.token), resolveProject(process.cwd(), opts.project));
|
|
246
|
+
}
|
|
247
|
+
catch (e) {
|
|
248
|
+
console.error(emailDomainErrorMessage(e));
|
|
249
|
+
process.exitCode = 1;
|
|
250
|
+
}
|
|
251
|
+
};
|
|
252
|
+
domain
|
|
253
|
+
.command('add')
|
|
254
|
+
.argument('<hostname>')
|
|
255
|
+
.option(project, projectHelp)
|
|
256
|
+
.description('Claim a domain and print the DNS records to add')
|
|
257
|
+
.action((hostname, opts) => runDomain((c, p) => runEmailDomainAdd(c, p, hostname))(opts));
|
|
258
|
+
domain
|
|
259
|
+
.command('status')
|
|
260
|
+
.option(project, projectHelp)
|
|
261
|
+
.description('List email domains and the records still to add')
|
|
262
|
+
.action((opts) => runDomain((c, p) => runEmailDomainStatus(c, p))(opts));
|
|
13
263
|
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { ApiClient, ApiError, friendlyApiError } from '../api.js';
|
|
2
|
+
import { requireCredentials, resolveProject } from '../config.js';
|
|
3
|
+
export async function getGithubStatus(client, projectId) {
|
|
4
|
+
return (await client.get(`/projects/${encodeURIComponent(projectId)}/github/status?ahead=1`));
|
|
5
|
+
}
|
|
6
|
+
export async function pullGithubBranch(client, projectId, branch) {
|
|
7
|
+
await client.post(`/projects/${encodeURIComponent(projectId)}/git/branches/${encodeURIComponent(branch)}/push`);
|
|
8
|
+
}
|
|
9
|
+
export function formatGithubBranches(status) {
|
|
10
|
+
if (!status.connected)
|
|
11
|
+
return 'This project is not linked to a GitHub repository.';
|
|
12
|
+
const repo = `${status.owner}/${status.name}`;
|
|
13
|
+
if (status.ahead === undefined)
|
|
14
|
+
return `Linked to ${repo}, but GitHub could not be read right now. Try again shortly.`;
|
|
15
|
+
if (status.ahead.length === 0)
|
|
16
|
+
return `Linked to ${repo}. Newly has every GitHub branch at its current tip.`;
|
|
17
|
+
return [
|
|
18
|
+
`Linked to ${repo}. On GitHub, not yet in Newly at their current tip:`,
|
|
19
|
+
...status.ahead.map((b) => ` ${b}`),
|
|
20
|
+
'Bring one in with: newly github pull <branch>',
|
|
21
|
+
].join('\n');
|
|
22
|
+
}
|
|
23
|
+
export function registerGithub(program) {
|
|
24
|
+
const github = program
|
|
25
|
+
.command('github')
|
|
26
|
+
.description("Work with the project's linked GitHub repository");
|
|
27
|
+
const run = (action) => async (opts) => {
|
|
28
|
+
try {
|
|
29
|
+
const creds = requireCredentials();
|
|
30
|
+
const projectId = resolveProject(process.cwd(), opts.project);
|
|
31
|
+
await action(new ApiClient(creds.apiUrl, creds.token), projectId);
|
|
32
|
+
}
|
|
33
|
+
catch (e) {
|
|
34
|
+
console.error(friendlyApiError(e));
|
|
35
|
+
process.exitCode = 1;
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
github
|
|
39
|
+
.command('branches')
|
|
40
|
+
.description('List GitHub branches that Newly does not have yet (or has at an older tip)')
|
|
41
|
+
.option('--project <id>', 'project id (defaults to the linked directory)')
|
|
42
|
+
.action(run(async (client, projectId) => {
|
|
43
|
+
console.log(formatGithubBranches(await getGithubStatus(client, projectId)));
|
|
44
|
+
}));
|
|
45
|
+
github
|
|
46
|
+
.command('pull <branch>')
|
|
47
|
+
.description('Bring one GitHub branch into Newly (a two-way sync of that branch) so it can be fetched and checked out')
|
|
48
|
+
.option('--project <id>', 'project id (defaults to the linked directory)')
|
|
49
|
+
.action((branch, opts) => run(async (client, projectId) => {
|
|
50
|
+
try {
|
|
51
|
+
await pullGithubBranch(client, projectId, branch);
|
|
52
|
+
}
|
|
53
|
+
catch (e) {
|
|
54
|
+
if (e instanceof ApiError && e.status === 403) {
|
|
55
|
+
throw new Error(`Pulling ${branch} needs a project admin. Ask the user to pull it from the GitHub pane in the Deploy tab.`);
|
|
56
|
+
}
|
|
57
|
+
throw e;
|
|
58
|
+
}
|
|
59
|
+
console.log(`${branch} is in Newly, synced both ways with GitHub. Run: git fetch origin ${branch} && git checkout ${branch}`);
|
|
60
|
+
})(opts));
|
|
61
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -10,6 +10,7 @@ import { registerPull } from './commands/pull.js';
|
|
|
10
10
|
import { registerDeploy } from './commands/deploy.js';
|
|
11
11
|
import { registerMisc } from './commands/misc.js';
|
|
12
12
|
import { registerStack } from './commands/stack.js';
|
|
13
|
+
import { registerGithub } from './commands/github.js';
|
|
13
14
|
import { registerCreds } from './commands/creds.js';
|
|
14
15
|
import { registerBuild } from './commands/build.js';
|
|
15
16
|
import { registerServices } from './commands/services.js';
|
|
@@ -37,6 +38,7 @@ registerDeploy(program);
|
|
|
37
38
|
registerAi(program);
|
|
38
39
|
registerMisc(program);
|
|
39
40
|
registerStack(program);
|
|
41
|
+
registerGithub(program);
|
|
40
42
|
registerCreds(program);
|
|
41
43
|
registerBuild(program);
|
|
42
44
|
registerServices(program);
|
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const CLI_VERSION = '0.2.
|
|
1
|
+
export const CLI_VERSION = '0.2.180';
|
package/package.json
CHANGED
|
Binary file
|
package/runcloud-0.2.178.tgz
DELETED
|
Binary file
|