eraspace 0.4.1 → 0.5.0

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.
Files changed (3) hide show
  1. package/README.md +2 -2
  2. package/bin.js +83 -44
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -15,7 +15,7 @@ npx eraspace deploy # prints the app URL
15
15
  ```
16
16
 
17
17
  A deploy without an account creates an unclaimed app. It works straight away and expires after about a week unless you
18
- sign in and claim it (`eraspace auth login`, then `eraspace claim`).
18
+ sign in and claim it (`eraspace login`, then `eraspace claim`).
19
19
 
20
20
  ## Commands
21
21
 
@@ -34,7 +34,7 @@ sign in and claim it (`eraspace auth login`, then `eraspace claim`).
34
34
  | `changes` | Print recent committed row changes as NDJSON (`--since <commit>`, `--limit <rows>`) |
35
35
  | `undo <commit> --yes` | Put every row changed after a commit back as it was; the undo is a commit too, so it can be undone |
36
36
  | `inspect` | Show the schema, functions and deploys |
37
- | `auth login` / `logout` / `whoami` | Sign in with your browser, sign out, or show who you are |
37
+ | `login` / `logout` / `whoami` (also under `auth`) | Sign in with your browser through Clerk, sign out, or show who you are. Sign-in listens once on `127.0.0.1`, so open the URL on the same machine; in CI use `ERASPACE_TOKEN` |
38
38
  | `claim` | Take ownership of this checkout's unclaimed app |
39
39
  | `apps` | List your apps |
40
40
  | `names` / `names add` / `names rm` | List, add or remove the app's names |
package/bin.js CHANGED
@@ -31559,7 +31559,7 @@ var TypeId53 = "~effect/Crypto";
31559
31559
  var Crypto = /* @__PURE__ */ Service("effect/Crypto");
31560
31560
  var make46 = (impl) => {
31561
31561
  const randomBytesUnsafe = impl.randomBytes;
31562
- const randomBytes4 = (size7) => map8(validateSize("randomBytes", size7), randomBytesUnsafe);
31562
+ const randomBytes5 = (size7) => map8(validateSize("randomBytes", size7), randomBytesUnsafe);
31563
31563
  const readUint53 = (bytes2) => (bytes2[0] & 31) * 2 ** 48 + bytes2[1] * 2 ** 40 + bytes2[2] * 2 ** 32 + bytes2[3] * 2 ** 24 + bytes2[4] * 2 ** 16 + bytes2[5] * 2 ** 8 + bytes2[6];
31564
31564
  const nextDoubleUnsafe = () => readUint53(randomBytesUnsafe(7)) / 2 ** 53;
31565
31565
  const nextIntUnsafe = () => {
@@ -31576,7 +31576,7 @@ var make46 = (impl) => {
31576
31576
  };
31577
31577
  return Crypto.of({
31578
31578
  [TypeId53]: TypeId53,
31579
- randomBytes: randomBytes4,
31579
+ randomBytes: randomBytes5,
31580
31580
  nextDoubleUnsafe,
31581
31581
  nextIntUnsafe,
31582
31582
  digest: impl.digest,
@@ -39365,7 +39365,7 @@ init_define_ERASPACE_MOVED_ORIGINS();
39365
39365
  // cli/package.json
39366
39366
  var package_default = {
39367
39367
  name: "eraspace-cli",
39368
- version: "0.4.1",
39368
+ version: "0.5.0",
39369
39369
  private: true,
39370
39370
  license: "MIT",
39371
39371
  type: "module",
@@ -39395,6 +39395,8 @@ var package_default = {
39395
39395
 
39396
39396
  // cli/src/auth.ts
39397
39397
  init_define_ERASPACE_MOVED_ORIGINS();
39398
+ import { createHash as createHash2, randomBytes as randomBytes4 } from "node:crypto";
39399
+ import { createServer } from "node:http";
39398
39400
 
39399
39401
  // node_modules/.pnpm/effect@4.0.5/node_modules/effect/dist/process/index.js
39400
39402
  init_define_ERASPACE_MOVED_ORIGINS();
@@ -39831,14 +39833,13 @@ var Token2 = Schema_exports.Struct({
39831
39833
  });
39832
39834
  var TokenList = Schema_exports.Struct({ tokens: Schema_exports.Array(Token2) });
39833
39835
  var CreatedToken = Schema_exports.Struct({ token: Schema_exports.String, id: Schema_exports.String });
39834
- var DeviceStart = Schema_exports.Struct({
39835
- deviceCode: Schema_exports.String,
39836
- userCode: Schema_exports.String,
39837
- verificationUri: Schema_exports.String,
39838
- interval: Schema_exports.optional(Schema_exports.Number),
39839
- expiresIn: Schema_exports.optional(Schema_exports.Number)
39840
- });
39841
- var DeviceToken = Schema_exports.Struct({
39836
+ var SignInConfig = Schema_exports.Struct({
39837
+ provider: Schema_exports.Literal("clerk"),
39838
+ issuer: Schema_exports.String,
39839
+ clientId: Schema_exports.String,
39840
+ scopes: Schema_exports.optional(Schema_exports.String)
39841
+ });
39842
+ var SignedIn = Schema_exports.Struct({
39842
39843
  token: Schema_exports.String,
39843
39844
  user: Schema_exports.Struct({ id: Schema_exports.String, login: Schema_exports.String, provider: Schema_exports.optional(Schema_exports.String) })
39844
39845
  });
@@ -39948,35 +39949,70 @@ var openBrowser = (url) => Effect_exports.gen(function* () {
39948
39949
  const handle = yield* spawner.spawn(ChildProcess_exports.make(command2, args2, { stdin: "ignore", stdout: "ignore", stderr: "ignore" }));
39949
39950
  yield* handle.unref;
39950
39951
  }).pipe(Effect_exports.scoped, Effect_exports.ignore);
39951
- var okDevice = ok(DeviceStart);
39952
- var login = Effect_exports.fnUntraced(function* (config2, browser) {
39953
- const start = yield* okDevice(config2, "POST", "/_api/auth/device", { auth: false });
39954
- yield* Console_exports.log(`Open ${c.bold(start.verificationUri)} and enter the code ${c.bold(start.userCode)}`);
39955
- if (browser) yield* openBrowser(start.verificationUri);
39956
- let interval = Math.max(1, start.interval ?? 5);
39957
- const deadline = (yield* Clock_exports.currentTimeMillis) + Math.max(1, start.expiresIn ?? 900) * 1e3;
39958
- let unreachable2 = 0;
39959
- while ((yield* Clock_exports.currentTimeMillis) < deadline) {
39960
- yield* Effect_exports.sleep(Duration_exports.seconds(interval));
39961
- const polled = yield* api(config2, "POST", "/_api/auth/device/token", { body: { deviceCode: start.deviceCode }, auth: false }).pipe(Effect_exports.result);
39962
- if (polled._tag === "Failure") {
39963
- if (++unreachable2 >= 5) return yield* Effect_exports.fail(polled.failure);
39964
- interval = Math.min(interval * 2, 30);
39965
- continue;
39966
- }
39967
- unreachable2 = 0;
39968
- const { status, json: json2 } = polled.success;
39969
- if (status === 202) {
39970
- if (json2.slowDown) interval += 5;
39971
- continue;
39952
+ var okConfig = ok(SignInConfig);
39953
+ var okSignedIn = ok(SignedIn);
39954
+ var SIGN_IN_TIMEOUT = Duration_exports.minutes(5);
39955
+ var page = (title, body) => `<!doctype html><html><head><meta charset="utf-8"><title>${title}</title></head><body style="font:15px system-ui;max-width:30rem;margin:3rem auto"><h1>${title}</h1><p>${body}</p></body></html>`;
39956
+ var loopback = Effect_exports.acquireRelease(
39957
+ Effect_exports.callback((resume) => {
39958
+ const server = createServer();
39959
+ server.once("error", (error2) => resume(fail12(`Could not start the local sign-in listener: ${describe(error2)}`)));
39960
+ server.listen(0, "127.0.0.1", () => resume(Effect_exports.succeed(server)));
39961
+ }),
39962
+ (server) => Effect_exports.sync(() => {
39963
+ server.close();
39964
+ server.closeAllConnections();
39965
+ })
39966
+ );
39967
+ var authorizationCode = (server, state) => Effect_exports.callback((resume) => {
39968
+ const onRequest = (request4, response) => {
39969
+ const url = new URL(request4.url ?? "/", "http://127.0.0.1");
39970
+ const answer = (status, title, body, done4) => response.writeHead(status, { "content-type": "text/html; charset=utf-8", "cache-control": "no-store" }).end(page(title, body), () => done4 && resume(done4));
39971
+ if (request4.method !== "GET" || url.pathname !== "/callback") return answer(404, "Not found", "This listener only waits for the eraspace sign-in.");
39972
+ if (url.searchParams.get("state") !== state) return answer(400, "Sign-in failed", "This link is not from the sign-in your terminal started.");
39973
+ const error2 = url.searchParams.get("error");
39974
+ if (error2) {
39975
+ const why = url.searchParams.get("error_description") || error2;
39976
+ return answer(400, "Sign-in failed", "Return to your terminal.", fail12(`Sign-in failed: ${why}. Run eraspace login again.`));
39972
39977
  }
39973
- if (status >= 400 || json2.ok === false) return yield* Effect_exports.fail(yield* failure(polled.success));
39974
- const result4 = yield* decode5(DeviceToken, json2, "sign-in");
39975
- yield* saveSession(config2.origin, { token: result4.token, user: result4.user, createdAt: yield* Clock_exports.currentTimeMillis });
39976
- yield* Console_exports.log(`Signed in to ${config2.origin} as ${c.bold(result4.user.login)}.`);
39977
- return;
39978
- }
39979
- return yield* fail12("The sign-in code expired. Run eraspace auth login again.");
39978
+ const code = url.searchParams.get("code");
39979
+ if (!code) return answer(400, "Sign-in failed", "Return to your terminal.", fail12("Sign-in failed: the provider sent no authorization code. Run eraspace login again."));
39980
+ answer(200, "Almost done", "You can close this tab and return to your terminal.", Effect_exports.succeed(code));
39981
+ };
39982
+ server.on("request", onRequest);
39983
+ return Effect_exports.sync(() => server.off("request", onRequest));
39984
+ });
39985
+ var base64url = (bytes2) => bytes2.toString("base64url");
39986
+ var login = Effect_exports.fnUntraced(function* (config2, browser) {
39987
+ const clerk = yield* okConfig(config2, "GET", "/_api/auth/config", { auth: false });
39988
+ const verifier = base64url(randomBytes4(32));
39989
+ const state = base64url(randomBytes4(32));
39990
+ const signedIn = yield* Effect_exports.scoped(
39991
+ Effect_exports.gen(function* () {
39992
+ const server = yield* loopback;
39993
+ const redirectUri = `http://127.0.0.1:${server.address().port}/callback`;
39994
+ const authorize = URL.parse(`${clerk.issuer.replace(/\/+$/, "")}/oauth/authorize`);
39995
+ if (authorize?.protocol !== "https:" && authorize?.protocol !== "http:") return yield* fail12(`The platform named an invalid sign-in provider: ${clerk.issuer}`);
39996
+ for (const [key, value4] of Object.entries({
39997
+ response_type: "code",
39998
+ client_id: clerk.clientId,
39999
+ redirect_uri: redirectUri,
40000
+ scope: clerk.scopes ?? "profile email",
40001
+ state,
40002
+ code_challenge: base64url(createHash2("sha256").update(verifier).digest()),
40003
+ code_challenge_method: "S256"
40004
+ })) authorize.searchParams.set(key, value4);
40005
+ yield* Console_exports.log(`Open this URL to sign in:
40006
+ ${c.bold(authorize.href)}`);
40007
+ if (browser) yield* openBrowser(authorize.href);
40008
+ const code = yield* authorizationCode(server, state).pipe(
40009
+ Effect_exports.timeoutOrElse({ duration: SIGN_IN_TIMEOUT, orElse: () => fail12("Sign-in timed out. Run eraspace login again.") })
40010
+ );
40011
+ return yield* okSignedIn(config2, "POST", "/_api/auth/clerk/token", { body: { code, codeVerifier: verifier, redirectUri }, auth: false });
40012
+ })
40013
+ );
40014
+ yield* saveSession(config2.origin, { token: signedIn.token, user: signedIn.user, createdAt: yield* Clock_exports.currentTimeMillis });
40015
+ yield* Console_exports.log(`Signed in to ${config2.origin} as ${c.bold(signedIn.user.login)}.`);
39980
40016
  });
39981
40017
  var LogoutResult = Schema_exports.Struct({ ok: Schema_exports.Literal(true) });
39982
40018
  var logout = Effect_exports.fnUntraced(function* (config2) {
@@ -41138,7 +41174,10 @@ var shadowFlags = {
41138
41174
  )
41139
41175
  };
41140
41176
  var browserFlag = {
41141
- browser: Flag_exports.Boolean("browser").pipe(Flag_exports.withDefault(true), Flag_exports.withDescription("Open the sign-in page in a browser; --no-browser only prints the URL"))
41177
+ browser: Flag_exports.Boolean("browser").pipe(
41178
+ Flag_exports.withDefault(true),
41179
+ Flag_exports.withDescription("Open the sign-in page in a browser; --no-browser only prints the URL, to open in a browser on this machine")
41180
+ )
41142
41181
  };
41143
41182
  var loginHandler = ({ browser }) => Effect_exports.flatMap(config, (c2) => login(c2, browser));
41144
41183
  var logoutHandler = () => Effect_exports.flatMap(config, logout);
@@ -41251,7 +41290,7 @@ var inspectCommand = Command_exports.make(
41251
41290
  ({ app }) => Effect_exports.flatMap(config, (c2) => inspect2(c2, value3(app)))
41252
41291
  ).pipe(Command_exports.withDescription("Show schema, functions and deployment details"));
41253
41292
  var authCommand = Command_exports.make("auth").pipe(
41254
- Command_exports.withDescription("Sign in with your browser (device flow), sign out, or show who you are"),
41293
+ Command_exports.withDescription("Sign in with your browser through Clerk, sign out, or show who you are"),
41255
41294
  Command_exports.withSubcommands([
41256
41295
  Command_exports.make("login", browserFlag, loginHandler).pipe(Command_exports.withDescription("Sign in to the platform")),
41257
41296
  Command_exports.make("logout", {}, logoutHandler).pipe(Command_exports.withDescription("Revoke and forget this platform's session")),
@@ -41334,8 +41373,8 @@ var tokenCommand = Command_exports.make("token").pipe(
41334
41373
  ).pipe(Command_exports.withDescription("Revoke a token"))
41335
41374
  ])
41336
41375
  );
41337
- var hidden = [
41338
- Command_exports.make("login", browserFlag, loginHandler).pipe(Command_exports.unlisted),
41376
+ var shortForms = [
41377
+ Command_exports.make("login", browserFlag, loginHandler).pipe(Command_exports.withDescription("Sign in with your browser through Clerk")),
41339
41378
  Command_exports.make("logout", {}, logoutHandler).pipe(Command_exports.unlisted),
41340
41379
  Command_exports.make("whoami", {}, whoamiHandler).pipe(Command_exports.unlisted)
41341
41380
  ];
@@ -41364,7 +41403,7 @@ var command = eraspace.pipe(
41364
41403
  changesCommand,
41365
41404
  undoCommand,
41366
41405
  tokenCommand,
41367
- ...hidden
41406
+ ...shortForms
41368
41407
  ])
41369
41408
  );
41370
41409
  var run6 = Command_exports.runWith(command, { version: VERSION });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eraspace",
3
- "version": "0.4.1",
3
+ "version": "0.5.0",
4
4
  "description": "eraspace: build, deploy and manage eraspace capsules",
5
5
  "type": "module",
6
6
  "keywords": [