runcloud 0.1.132 → 0.1.134

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.
@@ -4,7 +4,8 @@ import { ApiClient, ApiError, friendlyApiError } from '../api.js';
4
4
  import { requireCredentials, resolveProject } from '../config.js';
5
5
  import { createSnapshot } from '../archive.js';
6
6
  import { parseSseFrames } from '../sse.js';
7
- import { assertKnownServices, parseServiceFilter, readBackendServices } from '../services-manifest.js';
7
+ import { assertKnownServices, parseServiceFilter, readBackendServices, } from '../services-manifest.js';
8
+ import { buildDeployBundle, spawnRun, NEWLY_BACKEND_DEPLOY_BUNDLE_CONTENT_TYPE, NEWLY_BACKEND_DEPLOY_MODE_BUNDLE, } from '../deploy-bundle.js';
8
9
  const POLL_INTERVAL_MS = 5_000;
9
10
  const POLL_TIMEOUT_MS = 20 * 60 * 1000;
10
11
  export async function pollRun(client, runId, opts = {}) {
@@ -93,6 +94,32 @@ export async function runProdDeploy(projectId, client, gitSha = 'HEAD', services
93
94
  const run = await streamRun(client, run_id);
94
95
  finishOrThrow(run, 'prod');
95
96
  }
97
+ export function isBundleDeployMode(stackInfo) {
98
+ return stackInfo.deploy_mode === NEWLY_BACKEND_DEPLOY_MODE_BUNDLE;
99
+ }
100
+ export async function runBundleDeploy(projectId, client, dir, env, allServices, selected = [], runCmd = spawnRun) {
101
+ const targets = selected.length ? allServices.filter((s) => selected.includes(s.name)) : allServices;
102
+ const bundleServices = targets.map((s) => ({
103
+ name: s.name,
104
+ path: s.path,
105
+ schema: s.schema ?? undefined,
106
+ secrets: s.secrets,
107
+ }));
108
+ const bundle = await buildDeployBundle({
109
+ projectDir: dir,
110
+ services: bundleServices,
111
+ run: runCmd,
112
+ log: (line) => console.log(line),
113
+ });
114
+ const kib = Math.max(1, Math.round(bundle.length / 1024));
115
+ console.log(`Uploading deploy bundle (${kib} KiB)…`);
116
+ const params = new URLSearchParams({ env });
117
+ for (const service of selected)
118
+ params.append('services', service);
119
+ const { run_id } = await client.upload(`/newly_backend/deploy/${projectId}?${params}`, bundle, NEWLY_BACKEND_DEPLOY_BUNDLE_CONTENT_TYPE);
120
+ const run = await streamRun(client, run_id);
121
+ finishOrThrow(run, env);
122
+ }
96
123
  export async function ensureBackendProvisioned(client, projectId, env, opts = {}) {
97
124
  const print = opts.print ?? console.log;
98
125
  const status = await client.get(`/newly_backend/status/${projectId}`);
@@ -127,12 +154,14 @@ export function registerDeploy(program) {
127
154
  const projectId = resolveProject(process.cwd(), opts.project);
128
155
  const client = new ApiClient(creds.apiUrl, creds.token);
129
156
  const services = parseServiceFilter(opts.service);
130
- assertKnownServices(readBackendServices(process.cwd()), services);
131
- const stackInfo = await client.get(`/newly_backend/stack/${projectId}`);
157
+ const manifestServices = readBackendServices(process.cwd());
158
+ assertKnownServices(manifestServices, services);
159
+ const stackInfo = (await client.get(`/newly_backend/stack/${projectId}`));
132
160
  if (!stackInfo.cli_deployable) {
133
161
  throw new Error('This project does not support CLI deploys yet. Use the Newly web app to deploy it.');
134
162
  }
135
- if (opts.env === 'dev' && !existsSync(join(process.cwd(), 'backend'))) {
163
+ const bundleMode = isBundleDeployMode(stackInfo);
164
+ if ((opts.env === 'dev' || bundleMode) && !existsSync(join(process.cwd(), 'backend'))) {
136
165
  throw new Error('No backend/ in this directory. Run `newly pull <project-id>` first, ' +
137
166
  'or cd into a pulled project.');
138
167
  }
@@ -140,7 +169,12 @@ export function registerDeploy(program) {
140
169
  region: opts.region,
141
170
  print: console.log,
142
171
  });
143
- if (opts.env === 'prod') {
172
+ if (bundleMode) {
173
+ if (opts.gitSha !== 'HEAD')
174
+ console.log('Note: --git-sha is ignored; this project deploys the local backend code.');
175
+ await runBundleDeploy(projectId, client, process.cwd(), opts.env, manifestServices, services);
176
+ }
177
+ else if (opts.env === 'prod') {
144
178
  await runProdDeploy(projectId, client, opts.gitSha, services);
145
179
  }
146
180
  else {
@@ -1,86 +1,13 @@
1
- import { ApiClient, friendlyApiError } from '../api.js';
2
- import { requireCredentials, resolveProject } from '../config.js';
3
- import { assertDevOnly } from './misc.js';
4
- function makeClient(project) {
5
- const creds = requireCredentials();
6
- return {
7
- client: new ApiClient(creds.apiUrl, creds.token),
8
- projectId: resolveProject(process.cwd(), project),
9
- };
10
- }
11
- export async function getEmailStatus(client, projectId, env) {
12
- return await client.get(`/newly_backend/email/status?project_id=${encodeURIComponent(projectId)}&env=${env}`);
13
- }
14
- export function formatEmailStatus(status) {
15
- const lines = [
16
- `email: ${status.configured ? 'configured' : 'not configured'}`,
17
- `from: ${status.from ?? '(not set)'}`,
18
- ];
19
- if (status.requires_deploy)
20
- lines.push('requires deploy: yes');
21
- if (status.notes)
22
- lines.push(`notes: ${status.notes}`);
23
- return lines.join('\n');
24
- }
25
- export async function sendTestEmail(client, opts) {
26
- return await client.post('/newly_backend/email/test', {
27
- project_id: opts.projectId,
28
- env: opts.env,
29
- to: opts.to,
30
- from: opts.from,
31
- subject: opts.subject,
32
- text: opts.text,
33
- html: opts.html,
34
- });
35
- }
1
+ export const EMAIL_RETIRED_MESSAGE = '`newly email` was retired: it never had a server to talk to. ' +
2
+ 'There is no CLI replacement; send mail from your API service with your own provider.';
36
3
  export function registerEmail(program) {
37
- const email = program.command('email').description('Manage Newly Email');
38
- email
39
- .command('status')
40
- .description('Show Newly Email configuration for this backend')
41
- .option('--env <env>', 'dev or prod', 'dev')
42
- .option('--project <id>', 'project id')
43
- .action(async (opts) => {
44
- try {
45
- assertDevOnly(opts.env, 'email status');
46
- const { client, projectId } = makeClient(opts.project);
47
- console.log(formatEmailStatus(await getEmailStatus(client, projectId, opts.env)));
48
- }
49
- catch (e) {
50
- console.error(friendlyApiError(e));
51
- process.exitCode = 1;
52
- }
53
- });
54
- email
55
- .command('test')
56
- .description('Send a test email through the deployed API service')
57
- .requiredOption('--to <email>', 'recipient email address')
58
- .option('--from <email>', 'sender email address; defaults to the backend sender setting')
59
- .option('--subject <subject>', 'email subject', 'Newly email test')
60
- .option('--text <text>', 'plain-text body', 'This is a test email from your Newly backend.')
61
- .option('--html <html>', 'optional HTML body')
62
- .option('--env <env>', 'dev or prod', 'dev')
63
- .option('--project <id>', 'project id')
64
- .action(async (opts) => {
65
- try {
66
- assertDevOnly(opts.env, 'email test');
67
- const { client, projectId } = makeClient(opts.project);
68
- const result = await sendTestEmail(client, {
69
- projectId,
70
- env: opts.env,
71
- to: opts.to,
72
- from: opts.from,
73
- subject: opts.subject,
74
- text: opts.text,
75
- html: opts.html,
76
- });
77
- console.log(result.message_id
78
- ? `Email sent. message_id=${result.message_id}`
79
- : 'Email sent.');
80
- }
81
- catch (e) {
82
- console.error(friendlyApiError(e));
83
- process.exitCode = 1;
84
- }
4
+ program
5
+ .command('email')
6
+ .description('Retired: send mail from your API service instead')
7
+ .allowUnknownOption()
8
+ .allowExcessArguments()
9
+ .action(() => {
10
+ console.error(EMAIL_RETIRED_MESSAGE);
11
+ process.exitCode = 1;
85
12
  });
86
13
  }
@@ -1,12 +1,12 @@
1
1
  import { createInterface } from 'node:readline/promises';
2
2
  import { ApiClient, friendlyApiError } from '../api.js';
3
3
  import { runEnvironmentDatabaseShell } from '../shellSession.js';
4
- import { clearEnvironmentSelection, readEnvironmentSelection, controlPlaneUrl, requireCredentials, requireEnvironmentSelection, resolveProject, writeEnvironmentSelection, } from '../config.js';
4
+ import { clearEnvironmentSelection, readEnvironmentSelection, requireCredentials, requireEnvironmentSelection, resolveProject, writeEnvironmentSelection, } from '../config.js';
5
5
  const POLL_INTERVAL_MS = 2_000;
6
6
  const POLL_TIMEOUT_MS = 10 * 60 * 1_000;
7
7
  function client() {
8
8
  const credentials = requireCredentials();
9
- return new ApiClient(controlPlaneUrl(credentials), credentials.token);
9
+ return new ApiClient(credentials.apiUrl, credentials.token);
10
10
  }
11
11
  async function projectEnvironments(api, projectId) {
12
12
  const response = await api.get(`/projects/${encodeURIComponent(projectId)}/environments`);
@@ -248,7 +248,7 @@ export function registerEnvironment(program) {
248
248
  const projectId = resolveProject(process.cwd(), opts.project);
249
249
  const selected = requireEnvironmentSelection(process.cwd(), projectId);
250
250
  const credentials = requireCredentials();
251
- const environment = await new ApiClient(controlPlaneUrl(credentials), credentials.token).get(`/environments/${encodeURIComponent(selected.id)}`);
251
+ const environment = await new ApiClient(credentials.apiUrl, credentials.token).get(`/environments/${encodeURIComponent(selected.id)}`);
252
252
  const resource = resolveNamedResource(environment.databases, value, 'database');
253
253
  if (resource.status !== 'ready')
254
254
  throw new Error(`Database is not ready (${resource.status}).`);
@@ -49,9 +49,8 @@ export function openBrowser(url) {
49
49
  });
50
50
  child.unref();
51
51
  }
52
- export function commandApiUrls(apiUrl, authApiUrl) {
53
- const same = normalizeApiUrl(apiUrl) === normalizeApiUrl(authApiUrl);
54
- return { apiUrl, ...(same ? {} : { authApiUrl }) };
52
+ export function commandApiUrl(isRunCloud, authApiUrl, offered) {
53
+ return isRunCloud && offered ? offered : authApiUrl;
55
54
  }
56
55
  export async function emailLogin(apiUrl, presetEmail, io) {
57
56
  const email = presetEmail?.trim()
@@ -135,7 +134,6 @@ export function whoamiReport(creds) {
135
134
  const rows = [
136
135
  ['Account', creds.account ?? 'not recorded (browser sign-in never sees an address)'],
137
136
  ['API', creds.apiUrl],
138
- ...(creds.authApiUrl ? [['Issued by', creds.authApiUrl]] : []),
139
137
  ['Expires', expiresAtLabel(creds) ?? 'never'],
140
138
  ['Credential', credentialsDisplayPath()],
141
139
  ];
@@ -223,10 +221,8 @@ export function registerLogin(program) {
223
221
  let method = methodFromFlags(opts);
224
222
  const terminal = method === 'email' || !method ? acquirePromptIO() : null;
225
223
  try {
226
- let providers = null;
227
224
  if (!method && terminal) {
228
- providers = await fetchProviders(authApiUrl);
229
- const choices = loginChoices(providers);
225
+ const choices = loginChoices(await fetchProviders(authApiUrl));
230
226
  if (choices.length === 0) {
231
227
  throw new Error(`${authApiUrl} has no sign-in methods enabled.`);
232
228
  }
@@ -234,15 +230,14 @@ export function registerLogin(program) {
234
230
  }
235
231
  if (method === 'email') {
236
232
  const preset = typeof opts.email === 'string' ? opts.email : undefined;
237
- providers ??= await fetchProviders(authApiUrl);
238
233
  const { email, token } = await emailLogin(authApiUrl, preset, terminal?.io);
239
234
  saveCredentials({
240
235
  token,
236
+ apiUrl: authApiUrl,
241
237
  appUrl: opts.appUrl,
242
238
  expiresAt: Number.MAX_SAFE_INTEGER,
243
239
  revocable: true,
244
240
  account: email,
245
- ...commandApiUrls(providers.cliApiUrl ?? authApiUrl, authApiUrl),
246
241
  });
247
242
  console.log(`\n Signed in as ${email}`);
248
243
  console.log(` Credential saved to ${credentialsDisplayPath()}\n`);
@@ -274,11 +269,11 @@ export function registerLogin(program) {
274
269
  await verifyRunCloudCredential(authApiUrl, payload.token);
275
270
  saveCredentials({
276
271
  token: payload.token,
272
+ apiUrl: commandApiUrl(isRunCloud, authApiUrl, payload.apiUrl),
277
273
  appUrl: opts.appUrl,
278
274
  expiresAt: Math.floor(Date.now() / 1000) + payload.expiresIn,
279
275
  revocable: true,
280
276
  ...(payload.account ? { account: payload.account } : {}),
281
- ...commandApiUrls(payload.apiUrl, authApiUrl),
282
277
  });
283
278
  console.log(`Logged in${payload.account ? ` as ${payload.account}` : ''}. ` +
284
279
  `Credentials saved to ${credentialsDisplayPath()}`);
@@ -313,7 +308,7 @@ export function registerLogin(program) {
313
308
  .action(async () => {
314
309
  const creds = loadCredentials();
315
310
  if (creds?.revocable) {
316
- console.log(await revokeRunCloudCredential(creds.authApiUrl ?? creds.apiUrl, creds.token));
311
+ console.log(await revokeRunCloudCredential(creds.apiUrl, creds.token));
317
312
  }
318
313
  clearCredentials();
319
314
  console.log('Logged out.');
@@ -1,6 +1,6 @@
1
1
  import { acquirePromptIO, select, PromptAbortedError } from '../prompt.js';
2
2
  import { fetchProviders } from '../runCloudAuth.js';
3
- import { authApiUrlFor, commandApiUrls, emailLogin, loginChoices, methodFromFlags, openBrowser, verifyRunCloudCredential, } from './login.js';
3
+ import { authApiUrlFor, commandApiUrl, emailLogin, loginChoices, methodFromFlags, openBrowser, verifyRunCloudCredential, } from './login.js';
4
4
  export function tokenReport(isRunCloud, minted) {
5
5
  const bin = isRunCloud ? 'runcloud' : 'newly';
6
6
  const tokenVar = isRunCloud ? 'RUN_CLOUD_API_TOKEN' : 'NEWLY_API_TOKEN';
@@ -49,10 +49,8 @@ export function registerToken(program) {
49
49
  let method = methodFromFlags(opts);
50
50
  const terminal = method === 'email' || !method ? acquirePromptIO() : null;
51
51
  try {
52
- let providers = null;
53
52
  if (!method && terminal) {
54
- providers = await fetchProviders(authApiUrl);
55
- const choices = loginChoices(providers);
53
+ const choices = loginChoices(await fetchProviders(authApiUrl));
56
54
  if (choices.length === 0) {
57
55
  throw new Error(`${authApiUrl} has no sign-in methods enabled.`);
58
56
  }
@@ -60,11 +58,9 @@ export function registerToken(program) {
60
58
  }
61
59
  if (method === 'email') {
62
60
  const preset = typeof opts.email === 'string' ? opts.email : undefined;
63
- providers ??= await fetchProviders(authApiUrl);
64
61
  const { email, token: minted } = await emailLogin(authApiUrl, preset, terminal?.io);
65
62
  await verifyRunCloudCredential(authApiUrl, minted);
66
- const urls = commandApiUrls(providers.cliApiUrl ?? authApiUrl, authApiUrl);
67
- console.log(tokenReport(isRunCloud, { token: minted, apiUrl: urls.apiUrl, account: email }));
63
+ console.log(tokenReport(isRunCloud, { token: minted, apiUrl: authApiUrl, account: email }));
68
64
  return;
69
65
  }
70
66
  }
@@ -82,10 +78,9 @@ export function registerToken(program) {
82
78
  openBrowser,
83
79
  });
84
80
  await verifyRunCloudCredential(authApiUrl, payload.token);
85
- const urls = commandApiUrls(payload.apiUrl, authApiUrl);
86
81
  console.log(tokenReport(isRunCloud, {
87
82
  token: payload.token,
88
- apiUrl: urls.apiUrl,
83
+ apiUrl: commandApiUrl(isRunCloud, authApiUrl, payload.apiUrl),
89
84
  ...(payload.account ? { account: payload.account } : {}),
90
85
  }));
91
86
  }
package/dist/config.js CHANGED
@@ -36,9 +36,6 @@ export function loginCommandName() {
36
36
  export function normalizeApiUrl(url) {
37
37
  return url.replace(/\/+$/, '');
38
38
  }
39
- export function controlPlaneUrl(creds) {
40
- return normalizeApiUrl(creds.authApiUrl ?? creds.apiUrl);
41
- }
42
39
  export function saveCredentials(creds) {
43
40
  mkdirSync(credentialsHome(), { recursive: true, mode: 0o700 });
44
41
  const normalized = { ...creds, apiUrl: normalizeApiUrl(creds.apiUrl) };
@@ -67,10 +64,10 @@ export function loadCredentials() {
67
64
  if (!existsSync(credentialsPath()))
68
65
  return null;
69
66
  try {
70
- const creds = JSON.parse(readFileSync(credentialsPath(), 'utf8'));
67
+ const { authApiUrl, ...creds } = JSON.parse(readFileSync(credentialsPath(), 'utf8'));
71
68
  if (!creds.token || creds.expiresAt < Date.now() / 1000)
72
69
  return null;
73
- return { ...creds, apiUrl: normalizeApiUrl(creds.apiUrl) };
70
+ return { ...creds, apiUrl: normalizeApiUrl(authApiUrl ?? creds.apiUrl) };
74
71
  }
75
72
  catch {
76
73
  return null;
@@ -0,0 +1,148 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync, writeFileSync, } from 'node:fs';
3
+ import { tmpdir } from 'node:os';
4
+ import { join } from 'node:path';
5
+ import { Buffer } from 'node:buffer';
6
+ import { create as tarCreate } from 'tar';
7
+ export const NEWLY_BACKEND_DEPLOY_BUNDLE_CONTENT_TYPE = 'application/vnd.newly.deploy-bundle+gzip';
8
+ export const NEWLY_BACKEND_DEPLOY_MODE_BUNDLE = 'bundle';
9
+ const MANIFEST_PATH = 'newly-deploy.json';
10
+ const MAX_BUNDLE_BYTES = 25 * 1024 * 1024;
11
+ const REPLACE_TOKEN = /REPLACE_[A-Z_]+/g;
12
+ const OUTPUT_CAP_BYTES = 64 * 1024;
13
+ export class DeployBuildError extends Error {
14
+ constructor(message) {
15
+ super(message);
16
+ this.name = 'DeployBuildError';
17
+ }
18
+ }
19
+ export async function buildDeployBundle(options) {
20
+ const { projectDir, services, run, log } = options;
21
+ const bunVersion = await run('bun', ['--version'], projectDir);
22
+ if (bunVersion.code !== 0) {
23
+ throw new DeployBuildError('newly deploy needs bun on your PATH (https://bun.sh)');
24
+ }
25
+ const bundleDir = mkdtempSync(join(tmpdir(), 'newly-deploy-bundle-'));
26
+ const servicesDir = join(bundleDir, 'services');
27
+ mkdirSync(servicesDir, { recursive: true });
28
+ try {
29
+ const manifestServices = [];
30
+ for (const service of services) {
31
+ manifestServices.push(await buildService(service, projectDir, servicesDir, run, log));
32
+ }
33
+ const gitSha = await resolveGitSha(run, projectDir);
34
+ const manifest = { version: 1, git_sha: gitSha, services: manifestServices };
35
+ writeFileSync(join(bundleDir, MANIFEST_PATH), JSON.stringify(manifest, null, 2));
36
+ const bundle = await tarBundle(bundleDir);
37
+ if (bundle.length > MAX_BUNDLE_BYTES) {
38
+ const mib = (bundle.length / (1024 * 1024)).toFixed(1);
39
+ throw new DeployBuildError(`Deploy bundle is too large (${mib} MiB, max 25 MiB).`);
40
+ }
41
+ return bundle;
42
+ }
43
+ finally {
44
+ rmSync(bundleDir, { recursive: true, force: true });
45
+ }
46
+ }
47
+ async function buildService(service, projectDir, servicesDir, run, log) {
48
+ const dir = join(projectDir, 'backend', service.path);
49
+ if (!existsSync(dir)) {
50
+ throw new DeployBuildError(`No backend code to deploy (backend/${service.path} missing)`);
51
+ }
52
+ const configPath = join(dir, 'wrangler.toml');
53
+ if (!existsSync(configPath)) {
54
+ throw new DeployBuildError(`No wrangler.toml in backend/${service.path}`);
55
+ }
56
+ log(`Building ${service.name} (bun install, wrangler bundle)…`);
57
+ const install = await run('bun', ['install'], dir);
58
+ if (install.code !== 0) {
59
+ throw new DeployBuildError(`bun install failed for ${service.name}:\n${install.output}`);
60
+ }
61
+ const originalConfig = readFileSync(configPath, 'utf8');
62
+ const script = await wranglerBundle(service, dir, originalConfig, run);
63
+ const serviceDir = join(servicesDir, service.name);
64
+ mkdirSync(serviceDir, { recursive: true });
65
+ writeFileSync(join(serviceDir, 'index.js'), script);
66
+ writeFileSync(join(serviceDir, 'wrangler.toml'), originalConfig);
67
+ const migrations = stageMigrations(dir, serviceDir, service.name);
68
+ return {
69
+ name: service.name,
70
+ schema: service.schema ?? null,
71
+ secrets: service.secrets ?? null,
72
+ script: `services/${service.name}/index.js`,
73
+ config: `services/${service.name}/wrangler.toml`,
74
+ migrations,
75
+ };
76
+ }
77
+ async function wranglerBundle(service, dir, originalConfig, run) {
78
+ const deployConfigName = 'wrangler.newly-deploy.toml';
79
+ const deployConfigPath = join(dir, deployConfigName);
80
+ const outDir = mkdtempSync(join(tmpdir(), 'newly-deploy-wrangler-'));
81
+ try {
82
+ writeFileSync(deployConfigPath, originalConfig.replace(REPLACE_TOKEN, 'newly-placeholder'));
83
+ const dryRun = await run('bunx', ['wrangler', 'deploy', '--dry-run', '--outdir', outDir, '--config', deployConfigName], dir);
84
+ if (dryRun.code !== 0) {
85
+ throw new DeployBuildError(`wrangler bundle failed for ${service.name}:\n${dryRun.output}`);
86
+ }
87
+ const jsFiles = readdirSync(outDir).filter((f) => f.endsWith('.js'));
88
+ if (jsFiles.length !== 1) {
89
+ throw new DeployBuildError(`wrangler produced ${jsFiles.length} script file(s) for ${service.name}, expected exactly one`);
90
+ }
91
+ return readFileSync(join(outDir, jsFiles[0]));
92
+ }
93
+ finally {
94
+ rmSync(deployConfigPath, { force: true });
95
+ rmSync(outDir, { recursive: true, force: true });
96
+ }
97
+ }
98
+ function stageMigrations(dir, serviceDir, name) {
99
+ const drizzleDir = join(dir, 'drizzle');
100
+ const journalPath = join(drizzleDir, 'meta', '_journal.json');
101
+ if (!existsSync(journalPath))
102
+ return null;
103
+ const outMetaDir = join(serviceDir, 'drizzle', 'meta');
104
+ mkdirSync(outMetaDir, { recursive: true });
105
+ writeFileSync(join(outMetaDir, '_journal.json'), readFileSync(journalPath));
106
+ for (const entry of readdirSync(drizzleDir)) {
107
+ if (entry.endsWith('.sql')) {
108
+ writeFileSync(join(serviceDir, 'drizzle', entry), readFileSync(join(drizzleDir, entry)));
109
+ }
110
+ }
111
+ return `services/${name}/drizzle`;
112
+ }
113
+ async function resolveGitSha(run, projectDir) {
114
+ const result = await run('git', ['rev-parse', 'HEAD'], projectDir);
115
+ if (result.code !== 0)
116
+ return null;
117
+ const sha = result.output.trim();
118
+ return sha || null;
119
+ }
120
+ async function tarBundle(bundleDir) {
121
+ const chunks = [];
122
+ const stream = tarCreate({ gzip: true, cwd: bundleDir, portable: true }, [MANIFEST_PATH, 'services']);
123
+ for await (const chunk of stream) {
124
+ chunks.push(Buffer.from(chunk));
125
+ }
126
+ return Buffer.concat(chunks);
127
+ }
128
+ export function spawnRun(command, args, cwd) {
129
+ const shell = process.platform === 'win32';
130
+ const argv = shell ? args.map((arg) => (/[\s"]/.test(arg) ? `"${arg.replace(/"/g, '\\"')}"` : arg)) : args;
131
+ return new Promise((resolve) => {
132
+ const child = spawn(command, argv, {
133
+ cwd,
134
+ stdio: ['ignore', 'pipe', 'pipe'],
135
+ shell,
136
+ });
137
+ let output = '';
138
+ const append = (chunk) => {
139
+ output += chunk.toString('utf8');
140
+ if (output.length > OUTPUT_CAP_BYTES)
141
+ output = output.slice(-OUTPUT_CAP_BYTES);
142
+ };
143
+ child.stdout?.on('data', append);
144
+ child.stderr?.on('data', append);
145
+ child.on('error', (error) => resolve({ code: 127, output: error.message }));
146
+ child.on('close', (code) => resolve({ code: code ?? 1, output }));
147
+ });
148
+ }
@@ -41,13 +41,11 @@ export async function fetchProviders(apiUrl) {
41
41
  if (!response.ok)
42
42
  throw new AuthError(await readError(response), response.status);
43
43
  const payload = (await response.json());
44
- const cliApiUrl = typeof payload.cliApiUrl === 'string' ? payload.cliApiUrl.trim() : '';
45
44
  return {
46
45
  emailOtp: payload.emailOtp === true,
47
46
  google: payload.google === true,
48
47
  github: payload.github === true,
49
48
  apple: payload.apple === true,
50
- ...(cliApiUrl ? { cliApiUrl } : {}),
51
49
  };
52
50
  }
53
51
  export async function sendEmailCode(apiUrl, email) {
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const CLI_VERSION = '0.1.132';
1
+ export const CLI_VERSION = '0.1.134';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "runcloud",
3
- "version": "0.1.132",
3
+ "version": "0.1.134",
4
4
  "description": "Create and control run.cloud remote mobile simulators and cloud sandboxes",
5
5
  "license": "Apache-2.0",
6
6
  "keywords": [
Binary file
Binary file