@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 +8 -0
- package/dist/index.js +55 -5
- package/package.json +1 -1
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
|
|
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
|
-
|
|
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