@light-cloud/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 CHANGED
@@ -21,6 +21,14 @@ $ lc deploy
21
21
  └ Live at https://my-app.acme.light-cloud.io
22
22
  ```
23
23
 
24
+ ## No account yet? Start here
25
+
26
+ ```
27
+ $ npx @light-cloud/cli login --device --email you@example.com
28
+ ```
29
+
30
+ Type the 8-character code it prints at `console.light-cloud.com/device` from any device (approve the link in the email we send for a new address). That creates the account — free plan, no password, no form — and signs the terminal in. `lc billing card add --plan pro` later, when you need a paid plan; the card goes through a Stripe-hosted link, never through `lc`.
31
+
24
32
  ## Install
25
33
 
26
34
  ```bash
package/dist/index.js CHANGED
@@ -756,6 +756,16 @@ var LightCloudApi = class {
756
756
  environmentId
757
757
  });
758
758
  }
759
+ /** Visitor password gate. No password turns the gate off. */
760
+ async setEnvironmentPassword(organisationId, environmentId, password2) {
761
+ const enabled = Boolean(password2);
762
+ return this.client.post("/api/environments/password", {
763
+ targetOrganisationId: organisationId,
764
+ environmentId,
765
+ enabled,
766
+ ...enabled ? { password: password2 } : {}
767
+ });
768
+ }
759
769
  async createEnvironment(request) {
760
770
  return this.client.post("/api/environments/create", request);
761
771
  }
@@ -1696,6 +1706,21 @@ function packageSource(options = {}) {
1696
1706
  });
1697
1707
  }
1698
1708
 
1709
+ // src/lib/upload/storage-error.ts
1710
+ function describeStorageUploadFailure(response) {
1711
+ switch (response.status) {
1712
+ case 401:
1713
+ case 403:
1714
+ return `Light Cloud storage refused the upload (${response.status}). This is a platform-side fault, not your account, plan or permissions \u2014 deploy from a git repository for now and report it to Light Cloud support.`;
1715
+ case 400:
1716
+ return "the upload link was rejected, most likely expired \u2014 run the deploy again.";
1717
+ case 413:
1718
+ return "the archive is too large for a single upload \u2014 exclude build output and large assets, or deploy from a git repository.";
1719
+ default:
1720
+ return `Light Cloud storage answered ${response.status}${response.statusText ? ` ${response.statusText}` : ""} \u2014 try again in a moment.`;
1721
+ }
1722
+ }
1723
+
1699
1724
  // src/commands/shared.ts
1700
1725
  function contextFrom(command) {
1701
1726
  const options = command.optsWithGlobals();
@@ -1811,7 +1836,9 @@ async function uploadSource(ctx, directory, detection) {
1811
1836
  });
1812
1837
  if (!put.ok) {
1813
1838
  spin.stop("Upload failed", 1);
1814
- throw new CliError(`Upload rejected by storage (${put.status} ${put.statusText}).`, { hint: "Try again in a moment." });
1839
+ throw new CliError(`Upload failed: ${describeStorageUploadFailure(put)}`, {
1840
+ hint: put.status === 403 ? "Deploy from a git repository meanwhile: lc deploy --repo <owner/name>." : "Try again in a moment."
1841
+ });
1815
1842
  }
1816
1843
  await ctx.api.completeUpload(org.id, session.uploadId, {
1817
1844
  detectedFramework: detection.framework,
@@ -2101,6 +2128,8 @@ async function browserSignIn(consoleUrl, openIt) {
2101
2128
  out(`${c.dim("\u2502")} Open this link in your browser to sign in:`);
2102
2129
  }
2103
2130
  out(`${c.dim("\u2502")} ${link(flow.url)}`);
2131
+ if (isJson()) process.stderr.write(`Open this link to sign in: ${flow.url}
2132
+ `);
2104
2133
  const spin = spinner2();
2105
2134
  spin.start("Waiting for you to sign in\u2026");
2106
2135
  try {
@@ -2134,6 +2163,8 @@ async function deviceSignIn(client, emailFlag) {
2134
2163
  out(`${c.dim("\u2502")} ${c.bold(flow.userCode)}`);
2135
2164
  out(`${c.dim("\u2502")}`);
2136
2165
  out(`${c.dim("\u2502")} ${c.dim(`The code expires in ${minutes} minutes.`)}`);
2166
+ if (isJson()) process.stderr.write(`Enter code ${flow.userCode} at ${flow.verificationUrl} (expires in ${minutes} min)
2167
+ `);
2137
2168
  if (!flow.emailSent) {
2138
2169
  log.warn("The confirmation email could not be sent; an existing account can still approve by signing in on the page above.");
2139
2170
  }
@@ -3045,7 +3076,8 @@ function registerDeployCommand(program2) {
3045
3076
  out(`${c.dim("\u2502")} ${c.bold(app.name)} ${c.dim(sym.arrow)} ${env.name}${env.is_production ? c.accent(` ${sym.star}`) : ""}`);
3046
3077
  }
3047
3078
  let uploadId;
3048
- if (options.upload) {
3079
+ const linkedHere = isUploadApp && ctx.project?.config.applicationId === app.id;
3080
+ if (options.upload || linkedHere) {
3049
3081
  uploadId = await uploadForDeploy(ctx, options.dir);
3050
3082
  } else if (isUploadApp) {
3051
3083
  if (!isJson()) out(`${c.dim("\u2502")} Source: last upload ${c.dim("(pass --upload to send this folder)")}`);
@@ -3343,6 +3375,20 @@ function registerEnvCommands(program2) {
3343
3375
  if (result.dedicated_addon_notice) log.info(result.dedicated_addon_notice);
3344
3376
  });
3345
3377
  });
3378
+ env.command("password [env]").description("Gate the site behind a visitor password, or make it public again").option("-a, --app <app>", "app name or id").option("--set <password>", "password visitors must enter (6-128 characters)").option("--off", "remove the password gate").action(async (ref, options, command) => {
3379
+ const ctx = contextFrom(command);
3380
+ if (!options.set && !options.off) {
3381
+ throw new CliError("Nothing to change.", { hint: "Pass --set <password> or --off.", exitCode: EXIT.USAGE });
3382
+ }
3383
+ const app = await ctx.resolveApp(options.app);
3384
+ const target = await ctx.resolveEnv(app, ref);
3385
+ const org = await ctx.resolveOrg();
3386
+ const result = await ctx.api.setEnvironmentPassword(org.id, target.id, options.off ? void 0 : options.set);
3387
+ emit(result, () => {
3388
+ if (result.passwordEnabled) log.success(`${app.name} / ${target.name} is password protected.`);
3389
+ else log.success(`${app.name} / ${target.name} is public.`);
3390
+ });
3391
+ });
3346
3392
  const vars = env.command("vars").description("Environment variables");
3347
3393
  vars.command("list", { isDefault: true }).description("List variables (values masked unless --reveal)").option("-a, --app <app>", "app name or id").option("-e, --env <env>", "environment (default: production)").option("--reveal", "print values in clear").action(async (options, command) => {
3348
3394
  const ctx = contextFrom(command);
@@ -3492,7 +3538,6 @@ function printEnvDetails(env) {
3492
3538
  ["Memory", env.memory ?? void 0],
3493
3539
  ["Instances", env.min_instances != null || env.max_instances != null ? `${env.min_instances ?? 0} ${c.dim(sym.arrow)} ${env.max_instances ?? "?"}` : void 0],
3494
3540
  ["Password", env.password_enabled ? "protected" : void 0],
3495
- ["Region", env.cloud_run_region ?? void 0],
3496
3541
  ["Variables", `${Object.keys(env.environment_vars ?? {}).length}`],
3497
3542
  ["Last deploy", relativeTime(env.last_deployed_at)],
3498
3543
  ["ID", c.dim(env.id)]
@@ -3552,7 +3597,7 @@ function registerInitCommands(program2) {
3552
3597
  outro2(`Linked ${c.bold(app.name)} ${c.dim(sym.arrow)} ${c.dim(file)}`);
3553
3598
  });
3554
3599
  });
3555
- program2.command("create").description("Create an app from a git repository or a local folder (non-interactive friendly)").option("-n, --name <name>", "app name (default: folder or repository name)").option("-r, --repo <url>", "GitHub repository URL (default: the origin remote)").option("-b, --branch <branch>", "branch to deploy (default: current branch or main)").option("-u, --upload", "upload the folder instead of connecting a repository").option("-d, --dir <path>", "project folder", ".").option("-f, --framework <id>", "framework id (see `lc frameworks`)").option("-t, --type <type>", "deployment type: static or container").option("--build <command>", "build command").option("--output <dir>", "output directory for static sites").option("--port <port>", "container port").option("--root <dir>", "monorepo root directory inside the repository").option("--no-deploy", "create without deploying").option("--no-watch", "do not follow the first deployment").option("--no-link", "do not write a .lightcloud file").action(async (options, command) => {
3600
+ program2.command("create").description("Create an app from a git repository or a local folder (non-interactive friendly)").option("-n, --name <name>", "app name (default: folder or repository name)").option("-r, --repo <url>", "GitHub repository URL (default: the origin remote)").option("-b, --branch <branch>", "branch to deploy (default: current branch or main)").option("-u, --upload", "upload the folder instead of connecting a repository").option("-d, --dir <path>", "project folder", ".").option("-f, --framework <id>", "framework id (see `lc frameworks`)").option("-t, --type <type>", "deployment type: static or container").option("--build <command>", "build command").option("--output <dir>", "output directory for static sites").option("--port <port>", "container port").option("--root <dir>", "monorepo root directory inside the repository").option("--no-deploy", "create without deploying").option("--no-watch", "do not follow the first deployment").option("--no-link", "do not write a .lightcloud file").option("--password <password>", "gate the site behind a visitor password (6-128 characters)").action(async (options, command) => {
3556
3601
  const ctx = contextFrom(command);
3557
3602
  const directory = path9.resolve(options.dir);
3558
3603
  ctx.requireAuth();
@@ -3566,8 +3611,13 @@ function registerInitCommands(program2) {
3566
3611
  }
3567
3612
  let url = app.deployed_url ?? void 0;
3568
3613
  let status = app.status;
3614
+ const env = (app.environments ?? []).find((e) => e.is_production) ?? app.environments?.[0];
3615
+ if (options.password && env) {
3616
+ const org = await ctx.resolveOrg();
3617
+ await ctx.api.setEnvironmentPassword(org.id, env.id, options.password);
3618
+ if (!isJson()) log.info("Visitors will be asked for the password.");
3619
+ }
3569
3620
  if (options.deploy && options.watch) {
3570
- const env = (app.environments ?? []).find((e) => e.is_production) ?? app.environments?.[0];
3571
3621
  const org = await ctx.resolveOrg();
3572
3622
  const result = await watchResource(ctx.api, env ? { kind: "environment", id: env.id, organisationId: org.id } : { kind: "application", id: app.id, organisationId: org.id });
3573
3623
  url = result.update.deployed_url ?? url;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@light-cloud/cli",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Light Cloud command-line interface: deploy, watch, inspect and manage applications, environments and databases from the terminal.",
5
5
  "type": "module",
6
6
  "bin": {