@light-cloud/cli 0.1.0 → 0.1.3
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 +72 -8
- 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,9 +1836,12 @@ 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
|
+
spin.message("Inspecting source");
|
|
1844
|
+
const server = await ctx.api.completeUpload(org.id, session.uploadId, {
|
|
1817
1845
|
detectedFramework: detection.framework,
|
|
1818
1846
|
detectedRuntime: detection.runtime,
|
|
1819
1847
|
detectedDeploymentType: detection.deploymentType,
|
|
@@ -1821,7 +1849,11 @@ async function uploadSource(ctx, directory, detection) {
|
|
|
1821
1849
|
detectedOutputDirectory: detection.outputDirectory
|
|
1822
1850
|
});
|
|
1823
1851
|
spin.stop(`Uploaded ${pack.fileCount} files ${c.dim(`(${formatBytes(pack.buffer.length)})`)}`);
|
|
1824
|
-
|
|
1852
|
+
if (server.detectionSource === "server" && server.detectedFramework && server.detectedFramework !== detection.framework) {
|
|
1853
|
+
log.info(`Light Cloud read the source as ${server.detectedFramework} (${server.detectedDeploymentType}).`);
|
|
1854
|
+
}
|
|
1855
|
+
if (server.configWarning) log.warn(server.configWarning);
|
|
1856
|
+
return { uploadId: session.uploadId, fileCount: pack.fileCount, totalSize: pack.totalSize, archiveSize: pack.buffer.length, server };
|
|
1825
1857
|
}
|
|
1826
1858
|
function describeDetection(detection) {
|
|
1827
1859
|
const parts = [];
|
|
@@ -2101,6 +2133,8 @@ async function browserSignIn(consoleUrl, openIt) {
|
|
|
2101
2133
|
out(`${c.dim("\u2502")} Open this link in your browser to sign in:`);
|
|
2102
2134
|
}
|
|
2103
2135
|
out(`${c.dim("\u2502")} ${link(flow.url)}`);
|
|
2136
|
+
if (isJson()) process.stderr.write(`Open this link to sign in: ${flow.url}
|
|
2137
|
+
`);
|
|
2104
2138
|
const spin = spinner2();
|
|
2105
2139
|
spin.start("Waiting for you to sign in\u2026");
|
|
2106
2140
|
try {
|
|
@@ -2134,6 +2168,8 @@ async function deviceSignIn(client, emailFlag) {
|
|
|
2134
2168
|
out(`${c.dim("\u2502")} ${c.bold(flow.userCode)}`);
|
|
2135
2169
|
out(`${c.dim("\u2502")}`);
|
|
2136
2170
|
out(`${c.dim("\u2502")} ${c.dim(`The code expires in ${minutes} minutes.`)}`);
|
|
2171
|
+
if (isJson()) process.stderr.write(`Enter code ${flow.userCode} at ${flow.verificationUrl} (expires in ${minutes} min)
|
|
2172
|
+
`);
|
|
2137
2173
|
if (!flow.emailSent) {
|
|
2138
2174
|
log.warn("The confirmation email could not be sent; an existing account can still approve by signing in on the page above.");
|
|
2139
2175
|
}
|
|
@@ -3045,7 +3081,8 @@ function registerDeployCommand(program2) {
|
|
|
3045
3081
|
out(`${c.dim("\u2502")} ${c.bold(app.name)} ${c.dim(sym.arrow)} ${env.name}${env.is_production ? c.accent(` ${sym.star}`) : ""}`);
|
|
3046
3082
|
}
|
|
3047
3083
|
let uploadId;
|
|
3048
|
-
|
|
3084
|
+
const linkedHere = isUploadApp && ctx.project?.config.applicationId === app.id;
|
|
3085
|
+
if (options.upload || linkedHere) {
|
|
3049
3086
|
uploadId = await uploadForDeploy(ctx, options.dir);
|
|
3050
3087
|
} else if (isUploadApp) {
|
|
3051
3088
|
if (!isJson()) out(`${c.dim("\u2502")} Source: last upload ${c.dim("(pass --upload to send this folder)")}`);
|
|
@@ -3343,6 +3380,20 @@ function registerEnvCommands(program2) {
|
|
|
3343
3380
|
if (result.dedicated_addon_notice) log.info(result.dedicated_addon_notice);
|
|
3344
3381
|
});
|
|
3345
3382
|
});
|
|
3383
|
+
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) => {
|
|
3384
|
+
const ctx = contextFrom(command);
|
|
3385
|
+
if (!options.set && !options.off) {
|
|
3386
|
+
throw new CliError("Nothing to change.", { hint: "Pass --set <password> or --off.", exitCode: EXIT.USAGE });
|
|
3387
|
+
}
|
|
3388
|
+
const app = await ctx.resolveApp(options.app);
|
|
3389
|
+
const target = await ctx.resolveEnv(app, ref);
|
|
3390
|
+
const org = await ctx.resolveOrg();
|
|
3391
|
+
const result = await ctx.api.setEnvironmentPassword(org.id, target.id, options.off ? void 0 : options.set);
|
|
3392
|
+
emit(result, () => {
|
|
3393
|
+
if (result.passwordEnabled) log.success(`${app.name} / ${target.name} is password protected.`);
|
|
3394
|
+
else log.success(`${app.name} / ${target.name} is public.`);
|
|
3395
|
+
});
|
|
3396
|
+
});
|
|
3346
3397
|
const vars = env.command("vars").description("Environment variables");
|
|
3347
3398
|
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
3399
|
const ctx = contextFrom(command);
|
|
@@ -3492,7 +3543,6 @@ function printEnvDetails(env) {
|
|
|
3492
3543
|
["Memory", env.memory ?? void 0],
|
|
3493
3544
|
["Instances", env.min_instances != null || env.max_instances != null ? `${env.min_instances ?? 0} ${c.dim(sym.arrow)} ${env.max_instances ?? "?"}` : void 0],
|
|
3494
3545
|
["Password", env.password_enabled ? "protected" : void 0],
|
|
3495
|
-
["Region", env.cloud_run_region ?? void 0],
|
|
3496
3546
|
["Variables", `${Object.keys(env.environment_vars ?? {}).length}`],
|
|
3497
3547
|
["Last deploy", relativeTime(env.last_deployed_at)],
|
|
3498
3548
|
["ID", c.dim(env.id)]
|
|
@@ -3552,7 +3602,7 @@ function registerInitCommands(program2) {
|
|
|
3552
3602
|
outro2(`Linked ${c.bold(app.name)} ${c.dim(sym.arrow)} ${c.dim(file)}`);
|
|
3553
3603
|
});
|
|
3554
3604
|
});
|
|
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) => {
|
|
3605
|
+
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
3606
|
const ctx = contextFrom(command);
|
|
3557
3607
|
const directory = path9.resolve(options.dir);
|
|
3558
3608
|
ctx.requireAuth();
|
|
@@ -3566,8 +3616,13 @@ function registerInitCommands(program2) {
|
|
|
3566
3616
|
}
|
|
3567
3617
|
let url = app.deployed_url ?? void 0;
|
|
3568
3618
|
let status = app.status;
|
|
3619
|
+
const env = (app.environments ?? []).find((e) => e.is_production) ?? app.environments?.[0];
|
|
3620
|
+
if (options.password && env) {
|
|
3621
|
+
const org = await ctx.resolveOrg();
|
|
3622
|
+
await ctx.api.setEnvironmentPassword(org.id, env.id, options.password);
|
|
3623
|
+
if (!isJson()) log.info("Visitors will be asked for the password.");
|
|
3624
|
+
}
|
|
3569
3625
|
if (options.deploy && options.watch) {
|
|
3570
|
-
const env = (app.environments ?? []).find((e) => e.is_production) ?? app.environments?.[0];
|
|
3571
3626
|
const org = await ctx.resolveOrg();
|
|
3572
3627
|
const result = await watchResource(ctx.api, env ? { kind: "environment", id: env.id, organisationId: org.id } : { kind: "application", id: app.id, organisationId: org.id });
|
|
3573
3628
|
url = result.update.deployed_url ?? url;
|
|
@@ -3739,6 +3794,15 @@ async function createApp(ctx, directory, options, extra = {}) {
|
|
|
3739
3794
|
const name = await pickName(options.name ?? path9.basename(directory), interactive);
|
|
3740
3795
|
const definition = framework ? getFrameworkById(framework) : void 0;
|
|
3741
3796
|
const upload = await uploadSource(ctx, directory, { ...detection, framework, deploymentType, buildCommand, outputDirectory });
|
|
3797
|
+
const server = upload.server.detectionSource === "server" ? upload.server : void 0;
|
|
3798
|
+
if (server) {
|
|
3799
|
+
framework = options.framework ?? server.detectedFramework ?? framework;
|
|
3800
|
+
deploymentType = options.type ?? server.detectedDeploymentType ?? deploymentType;
|
|
3801
|
+
buildCommand = options.build ?? server.detectedBuildCommand ?? buildCommand;
|
|
3802
|
+
outputDirectory = options.output ?? server.detectedOutputDirectory ?? outputDirectory;
|
|
3803
|
+
containerPort = options.port ? Number(options.port) : server.detectedContainerPort ?? containerPort;
|
|
3804
|
+
}
|
|
3805
|
+
const finalDefinition = framework ? getFrameworkById(framework) : definition;
|
|
3742
3806
|
const spinCreate = spinner2();
|
|
3743
3807
|
spinCreate.start(`Creating ${name}`);
|
|
3744
3808
|
const app = await ctx.api.createFromUpload({
|
|
@@ -3747,7 +3811,7 @@ async function createApp(ctx, directory, options, extra = {}) {
|
|
|
3747
3811
|
uploadId: upload.uploadId,
|
|
3748
3812
|
deploymentType,
|
|
3749
3813
|
framework: framework ?? (detection.hasDockerfile ? "custom" : void 0),
|
|
3750
|
-
runtime:
|
|
3814
|
+
runtime: server?.detectedRuntime ?? finalDefinition?.runtime ?? (detection.hasDockerfile ? "custom" : void 0),
|
|
3751
3815
|
buildCommand,
|
|
3752
3816
|
outputDirectory: deploymentType === "static" ? outputDirectory : void 0,
|
|
3753
3817
|
containerPort: deploymentType === "container" ? containerPort : void 0
|
package/package.json
CHANGED