runcloud 0.2.164 → 0.2.165

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.
@@ -106,6 +106,7 @@ export async function runBundleDeploy(projectId, client, dir, env, allServices,
106
106
  secrets: s.secrets,
107
107
  }));
108
108
  const bundle = await buildDeployBundle({
109
+ projectId,
109
110
  projectDir: dir,
110
111
  services: bundleServices,
111
112
  run: runCmd,
@@ -144,7 +145,7 @@ export function registerDeploy(program) {
144
145
  .option('--region <region>', "Newly server region hint for first-time dev provisioning (defaults to 'auto')", 'auto')
145
146
  .option('--project <id>', 'project id (defaults to the linked directory)')
146
147
  .option('--service <names>', 'comma-separated service names from backend/newly.services.json')
147
- .option('--git-sha <sha>', 'prod git revision to deploy from canonical project code', 'HEAD')
148
+ .option('--git-sha <sha>', 'legacy prod deploys only; ignored when the project deploys your local backend/ folder', 'HEAD')
148
149
  .option('--yes', 'skip the prod confirmation prompt', false)
149
150
  .action(async (opts) => {
150
151
  try {
@@ -10,6 +10,7 @@ export function registerLink(program) {
10
10
  writeProjectLink(dir, {
11
11
  projectId,
12
12
  pulledSha: existing?.projectId === projectId ? existing.pulledSha : null,
13
+ pulledBackendHash: existing?.projectId === projectId ? existing.pulledBackendHash : undefined,
13
14
  pulledAt: new Date().toISOString(),
14
15
  });
15
16
  console.log(`Linked ${dir} to project ${projectId}. You can now run \`newly deploy\`, \`newly status\`, etc. here.`);
@@ -1,7 +1,9 @@
1
- import { readdirSync } from 'node:fs';
1
+ import { existsSync, readdirSync } from 'node:fs';
2
+ import { join } from 'node:path';
2
3
  import { ApiClient, friendlyApiError } from '../api.js';
3
4
  import { requireCredentials, resolveProject, writeProjectLink } from '../config.js';
4
- import { extractSnapshot, pruneStaleTopLevel, snapshotTopLevelEntries } from '../archive.js';
5
+ import { extractSnapshot, listSnapshot, pruneStaleTopLevel, snapshotTopLevelEntries } from '../archive.js';
6
+ import { hashBackendSource } from '../deploy-bundle.js';
5
7
  export function assertPullTarget(dir, force) {
6
8
  const entries = readdirSync(dir).filter((e) => e !== '.newly');
7
9
  if (entries.length > 0 && !force) {
@@ -38,10 +40,12 @@ export function registerPull(program) {
38
40
  }
39
41
  await extractTarball(data, dir);
40
42
  const pulledBranch = res.headers.get('x-newly-branch');
43
+ const pulledPaths = new Set((await listSnapshot(data)).map((name) => name.replace(/^\.?\//, '')));
41
44
  writeProjectLink(dir, {
42
45
  projectId,
43
46
  pulledSha: res.headers.get('x-newly-head-sha'),
44
47
  pulledAt: new Date().toISOString(),
48
+ pulledBackendHash: existsSync(join(dir, 'backend')) ? hashBackendSource(dir, pulledPaths) : undefined,
45
49
  });
46
50
  console.log(`Pulled${pulledBranch ? ` branch ${pulledBranch}` : ''} into ${dir} and linked to project ${projectId}.`);
47
51
  }
@@ -1,9 +1,11 @@
1
1
  import { spawn } from 'node:child_process';
2
+ import { createHash } from 'node:crypto';
2
3
  import { existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync, writeFileSync, } from 'node:fs';
3
4
  import { tmpdir } from 'node:os';
4
5
  import { join } from 'node:path';
5
6
  import { Buffer } from 'node:buffer';
6
7
  import { create as tarCreate } from 'tar';
8
+ import { readProjectLink } from './config.js';
7
9
  export const NEWLY_BACKEND_DEPLOY_BUNDLE_CONTENT_TYPE = 'application/vnd.newly.deploy-bundle+gzip';
8
10
  export const NEWLY_BACKEND_DEPLOY_MODE_BUNDLE = 'bundle';
9
11
  const MANIFEST_PATH = 'newly-deploy.json';
@@ -17,7 +19,7 @@ export class DeployBuildError extends Error {
17
19
  }
18
20
  }
19
21
  export async function buildDeployBundle(options) {
20
- const { projectDir, services, run, log } = options;
22
+ const { projectId, projectDir, services, run, log } = options;
21
23
  const bunVersion = await run('bun', ['--version'], projectDir);
22
24
  if (bunVersion.code !== 0) {
23
25
  throw new DeployBuildError('newly deploy needs bun on your PATH (https://bun.sh)');
@@ -26,11 +28,11 @@ export async function buildDeployBundle(options) {
26
28
  const servicesDir = join(bundleDir, 'services');
27
29
  mkdirSync(servicesDir, { recursive: true });
28
30
  try {
31
+ const gitSha = await resolveGitSha(run, projectDir, projectId);
29
32
  const manifestServices = [];
30
33
  for (const service of services) {
31
34
  manifestServices.push(await buildService(service, projectDir, servicesDir, run, log));
32
35
  }
33
- const gitSha = await resolveGitSha(run, projectDir);
34
36
  const manifest = { version: 1, git_sha: gitSha, services: manifestServices };
35
37
  writeFileSync(join(bundleDir, MANIFEST_PATH), JSON.stringify(manifest, null, 2));
36
38
  const bundle = await tarBundle(bundleDir);
@@ -110,12 +112,40 @@ function stageMigrations(dir, serviceDir, name) {
110
112
  }
111
113
  return `services/${name}/drizzle`;
112
114
  }
113
- async function resolveGitSha(run, projectDir) {
114
- const result = await run('git', ['rev-parse', 'HEAD'], projectDir);
115
- if (result.code !== 0)
115
+ async function resolveGitSha(run, projectDir, projectId) {
116
+ const link = readProjectLink(projectDir);
117
+ if (link?.projectId === projectId && link.pulledSha && link.pulledBackendHash) {
118
+ try {
119
+ if (link.pulledBackendHash === hashBackendSource(projectDir))
120
+ return link.pulledSha;
121
+ }
122
+ catch {
123
+ }
124
+ }
125
+ const head = await run('git', ['rev-parse', 'HEAD'], projectDir);
126
+ const sha = head.code === 0 ? head.output.trim() : '';
127
+ if (!sha)
128
+ return null;
129
+ const tracked = await run('git', ['ls-files', '--error-unmatch', '--', 'backend'], projectDir);
130
+ if (tracked.code !== 0)
116
131
  return null;
117
- const sha = result.output.trim();
118
- return sha || null;
132
+ const changes = await run('git', ['--no-optional-locks', 'status', '--porcelain', '--', 'backend'], projectDir);
133
+ return changes.code === 0 && changes.output.trim() === '' ? sha : null;
134
+ }
135
+ export function hashBackendSource(projectDir, only) {
136
+ const digest = (data) => createHash('sha256').update(data).digest('hex');
137
+ const files = backendSourceFiles(projectDir, 'backend').filter((path) => !only || only.has(path));
138
+ return digest(files.sort().map((path) => `${path}\0${digest(readFileSync(join(projectDir, path)))}`).join('\n'));
139
+ }
140
+ function backendSourceFiles(projectDir, rel) {
141
+ return readdirSync(join(projectDir, rel), { withFileTypes: true }).flatMap((entry) => {
142
+ if (entry.name.startsWith('.') || entry.name === 'node_modules')
143
+ return [];
144
+ const path = `${rel}/${entry.name}`;
145
+ if (entry.isDirectory())
146
+ return backendSourceFiles(projectDir, path);
147
+ return entry.isFile() ? [path] : [];
148
+ });
119
149
  }
120
150
  async function tarBundle(bundleDir) {
121
151
  const chunks = [];
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const CLI_VERSION = '0.2.164';
1
+ export const CLI_VERSION = '0.2.165';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "runcloud",
3
- "version": "0.2.164",
3
+ "version": "0.2.165",
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