@urateam/cli 0.1.2 → 0.1.5

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 (49) hide show
  1. package/dist/__tests__/admin-commands.test.d.ts +2 -0
  2. package/dist/__tests__/admin-commands.test.d.ts.map +1 -0
  3. package/dist/__tests__/admin-commands.test.js +66 -0
  4. package/dist/__tests__/admin-commands.test.js.map +1 -0
  5. package/dist/__tests__/helpers/license.d.ts +3 -0
  6. package/dist/__tests__/helpers/license.d.ts.map +1 -0
  7. package/dist/__tests__/helpers/license.js +66 -0
  8. package/dist/__tests__/helpers/license.js.map +1 -0
  9. package/dist/__tests__/license-command.test.d.ts +2 -0
  10. package/dist/__tests__/license-command.test.d.ts.map +1 -0
  11. package/dist/__tests__/license-command.test.js +114 -0
  12. package/dist/__tests__/license-command.test.js.map +1 -0
  13. package/dist/__tests__/sso-bootstrap.test.d.ts +2 -0
  14. package/dist/__tests__/sso-bootstrap.test.d.ts.map +1 -0
  15. package/dist/__tests__/sso-bootstrap.test.js +94 -0
  16. package/dist/__tests__/sso-bootstrap.test.js.map +1 -0
  17. package/dist/__tests__/version.test.d.ts +2 -0
  18. package/dist/__tests__/version.test.d.ts.map +1 -0
  19. package/dist/__tests__/version.test.js +14 -0
  20. package/dist/__tests__/version.test.js.map +1 -0
  21. package/dist/commands/admin.d.ts +16 -0
  22. package/dist/commands/admin.d.ts.map +1 -0
  23. package/dist/commands/admin.js +110 -0
  24. package/dist/commands/admin.js.map +1 -0
  25. package/dist/commands/dev.d.ts.map +1 -1
  26. package/dist/commands/dev.js +29 -0
  27. package/dist/commands/dev.js.map +1 -1
  28. package/dist/commands/license.d.ts +18 -0
  29. package/dist/commands/license.d.ts.map +1 -0
  30. package/dist/commands/license.js +80 -0
  31. package/dist/commands/license.js.map +1 -0
  32. package/dist/commands/run.d.ts.map +1 -1
  33. package/dist/commands/run.js +13 -0
  34. package/dist/commands/run.js.map +1 -1
  35. package/dist/commands/start.d.ts.map +1 -1
  36. package/dist/commands/start.js +71 -29
  37. package/dist/commands/start.js.map +1 -1
  38. package/dist/index.js +21 -1
  39. package/dist/index.js.map +1 -1
  40. package/dist/sso-bootstrap.d.ts +7 -0
  41. package/dist/sso-bootstrap.d.ts.map +1 -0
  42. package/dist/sso-bootstrap.js +86 -0
  43. package/dist/sso-bootstrap.js.map +1 -0
  44. package/dist/version.d.ts +2 -0
  45. package/dist/version.d.ts.map +1 -0
  46. package/dist/version.js +12 -0
  47. package/dist/version.js.map +1 -0
  48. package/package.json +4 -3
  49. package/LICENSE +0 -27
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=admin-commands.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"admin-commands.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/admin-commands.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,66 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from "vitest";
2
+ import { createDb } from "@urateam/core";
3
+ import { dashboardUsers } from "@urateam/core/dist/db/schema.js";
4
+ import { runAdminList, runAdminGrant, runAdminRevoke, } from "../commands/admin.js";
5
+ import { installTestProLicense, restoreLicense, } from "./helpers/license.js";
6
+ let db;
7
+ let logs;
8
+ const log = (s) => {
9
+ logs.push(s);
10
+ };
11
+ beforeEach(async () => {
12
+ logs = [];
13
+ await installTestProLicense("enterprise");
14
+ db = await createDb({ connectionString: ":memory:" });
15
+ await db.insert(dashboardUsers).values([
16
+ {
17
+ id: "u_admin",
18
+ email: "admin@b.com",
19
+ name: "Admin",
20
+ workosUserId: null,
21
+ role: "admin",
22
+ },
23
+ {
24
+ id: "u_op",
25
+ email: "op@b.com",
26
+ name: "Op",
27
+ workosUserId: null,
28
+ role: "operator",
29
+ },
30
+ ]);
31
+ });
32
+ afterEach(async () => {
33
+ await restoreLicense();
34
+ });
35
+ describe("ura admin commands", () => {
36
+ it("list prints all users", async () => {
37
+ await runAdminList({ db, log });
38
+ expect(logs.some((l) => l.includes("admin@b.com"))).toBe(true);
39
+ expect(logs.some((l) => l.includes("op@b.com"))).toBe(true);
40
+ });
41
+ it("grant promotes by email", async () => {
42
+ await runAdminGrant({ db, email: "op@b.com", newRole: "admin", log });
43
+ const rows = await db.select().from(dashboardUsers);
44
+ expect(rows.find((u) => u.id === "u_op").role).toBe("admin");
45
+ });
46
+ it("grant with unknown email errors clearly", async () => {
47
+ await expect(runAdminGrant({
48
+ db,
49
+ email: "ghost@nowhere.com",
50
+ newRole: "admin",
51
+ log,
52
+ })).rejects.toThrow(/user not found/i);
53
+ });
54
+ it("revoke sets role to viewer", async () => {
55
+ // First promote op to admin so a revoke → viewer is a real change
56
+ await runAdminGrant({ db, email: "op@b.com", newRole: "admin", log });
57
+ await runAdminRevoke({ db, email: "op@b.com", log });
58
+ const rows = await db.select().from(dashboardUsers);
59
+ expect(rows.find((u) => u.id === "u_op").role).toBe("viewer");
60
+ });
61
+ it("refuses when feature is unlicensed", async () => {
62
+ await restoreLicense();
63
+ await expect(runAdminList({ db, log })).rejects.toThrow(/enterprise/i);
64
+ });
65
+ });
66
+ //# sourceMappingURL=admin-commands.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"admin-commands.test.js","sourceRoot":"","sources":["../../src/__tests__/admin-commands.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AACrE,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AACjE,OAAO,EACL,YAAY,EACZ,aAAa,EACb,cAAc,GACf,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EACL,qBAAqB,EACrB,cAAc,GACf,MAAM,sBAAsB,CAAC;AAE9B,IAAI,EAAO,CAAC;AACZ,IAAI,IAAc,CAAC;AACnB,MAAM,GAAG,GAAG,CAAC,CAAS,EAAE,EAAE;IACxB,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AACf,CAAC,CAAC;AAEF,UAAU,CAAC,KAAK,IAAI,EAAE;IACpB,IAAI,GAAG,EAAE,CAAC;IACV,MAAM,qBAAqB,CAAC,YAAY,CAAC,CAAC;IAC1C,EAAE,GAAG,MAAM,QAAQ,CAAC,EAAE,gBAAgB,EAAE,UAAU,EAAE,CAAC,CAAC;IACtD,MAAM,EAAE,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC,MAAM,CAAC;QACrC;YACE,EAAE,EAAE,SAAS;YACb,KAAK,EAAE,aAAa;YACpB,IAAI,EAAE,OAAO;YACb,YAAY,EAAE,IAAI;YAClB,IAAI,EAAE,OAAO;SACd;QACD;YACE,EAAE,EAAE,MAAM;YACV,KAAK,EAAE,UAAU;YACjB,IAAI,EAAE,IAAI;YACV,YAAY,EAAE,IAAI;YAClB,IAAI,EAAE,UAAU;SACjB;KACF,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,SAAS,CAAC,KAAK,IAAI,EAAE;IACnB,MAAM,cAAc,EAAE,CAAC;AACzB,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,oBAAoB,EAAE,GAAG,EAAE;IAClC,EAAE,CAAC,uBAAuB,EAAE,KAAK,IAAI,EAAE;QACrC,MAAM,YAAY,CAAC,EAAE,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC;QAChC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC/D,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC9D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,yBAAyB,EAAE,KAAK,IAAI,EAAE;QACvC,MAAM,aAAa,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,CAAC;QACtE,MAAM,IAAI,GAAG,MAAM,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;QACpD,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACpE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,yCAAyC,EAAE,KAAK,IAAI,EAAE;QACvD,MAAM,MAAM,CACV,aAAa,CAAC;YACZ,EAAE;YACF,KAAK,EAAE,mBAAmB;YAC1B,OAAO,EAAE,OAAO;YAChB,GAAG;SACJ,CAAC,CACH,CAAC,OAAO,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAC;IACvC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,4BAA4B,EAAE,KAAK,IAAI,EAAE;QAC1C,kEAAkE;QAClE,MAAM,aAAa,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,CAAC;QACtE,MAAM,cAAc,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE,GAAG,EAAE,CAAC,CAAC;QACrD,MAAM,IAAI,GAAG,MAAM,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;QACpD,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACrE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oCAAoC,EAAE,KAAK,IAAI,EAAE;QAClD,MAAM,cAAc,EAAE,CAAC;QACvB,MAAM,MAAM,CAAC,YAAY,CAAC,EAAE,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC;IACzE,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,3 @@
1
+ export declare function installTestProLicense(tier?: "pro" | "enterprise"): Promise<void>;
2
+ export declare function restoreLicense(): Promise<void>;
3
+ //# sourceMappingURL=license.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"license.d.ts","sourceRoot":"","sources":["../../../src/__tests__/helpers/license.ts"],"names":[],"mappings":"AAuBA,wBAAsB,qBAAqB,CACzC,IAAI,GAAE,KAAK,GAAG,YAA2B,GACxC,OAAO,CAAC,IAAI,CAAC,CA8Bf;AAED,wBAAsB,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC,CAiBpD"}
@@ -0,0 +1,66 @@
1
+ import { generateKeyPairSync, sign } from "node:crypto";
2
+ import { _resetLicenseCache } from "@urateam/core/dist/license.js";
3
+ function b64url(buf) {
4
+ return buf
5
+ .toString("base64")
6
+ .replace(/\+/g, "-")
7
+ .replace(/\//g, "_")
8
+ .replace(/=+$/, "");
9
+ }
10
+ function makeJwt(privateKey, payload) {
11
+ const header = { alg: "EdDSA", typ: "JWT" };
12
+ const headerB64 = b64url(Buffer.from(JSON.stringify(header)));
13
+ const payloadB64 = b64url(Buffer.from(JSON.stringify(payload)));
14
+ const signingInput = `${headerB64}.${payloadB64}`;
15
+ const sig = sign(null, Buffer.from(signingInput), privateKey);
16
+ return `${signingInput}.${b64url(sig)}`;
17
+ }
18
+ let savedPublicKey;
19
+ let savedEnv;
20
+ export async function installTestProLicense(tier = "enterprise") {
21
+ const { publicKey, privateKey } = generateKeyPairSync("ed25519");
22
+ const publicKeyB64 = Buffer.from(publicKey.export({ format: "der", type: "spki" })).toString("base64");
23
+ const mod = await import("@urateam/core/dist/license-public-key.js");
24
+ if (savedPublicKey === undefined) {
25
+ savedPublicKey = mod
26
+ .LICENSE_PUBLIC_KEY_DER_B64;
27
+ }
28
+ Object.defineProperty(mod, "LICENSE_PUBLIC_KEY_DER_B64", {
29
+ value: publicKeyB64,
30
+ writable: true,
31
+ configurable: true,
32
+ });
33
+ const now = Math.floor(Date.now() / 1000);
34
+ const jwt = makeJwt(privateKey, {
35
+ iss: "urateams.com",
36
+ sub: "cust_test",
37
+ tier,
38
+ seats: 25,
39
+ iat: now,
40
+ exp: now + 86_400,
41
+ });
42
+ if (savedEnv === undefined)
43
+ savedEnv = process.env.URATEAM_LICENSE_KEY;
44
+ process.env.URATEAM_LICENSE_KEY = jwt;
45
+ _resetLicenseCache();
46
+ }
47
+ export async function restoreLicense() {
48
+ if (savedPublicKey !== undefined) {
49
+ const mod = await import("@urateam/core/dist/license-public-key.js");
50
+ Object.defineProperty(mod, "LICENSE_PUBLIC_KEY_DER_B64", {
51
+ value: savedPublicKey,
52
+ writable: true,
53
+ configurable: true,
54
+ });
55
+ savedPublicKey = undefined;
56
+ }
57
+ if (savedEnv === undefined) {
58
+ delete process.env.URATEAM_LICENSE_KEY;
59
+ }
60
+ else {
61
+ process.env.URATEAM_LICENSE_KEY = savedEnv;
62
+ savedEnv = undefined;
63
+ }
64
+ _resetLicenseCache();
65
+ }
66
+ //# sourceMappingURL=license.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"license.js","sourceRoot":"","sources":["../../../src/__tests__/helpers/license.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=license-command.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"license-command.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/license-command.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,114 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from "vitest";
2
+ import { generateKeyPairSync, createPublicKey, verify } from "node:crypto";
3
+ import { issueLicense } from "../commands/license.js";
4
+ describe("issueLicense", () => {
5
+ let publicKeyDer;
6
+ let originalSigningKey;
7
+ beforeEach(() => {
8
+ const { publicKey, privateKey } = generateKeyPairSync("ed25519");
9
+ publicKeyDer = Buffer.from(publicKey.export({ format: "der", type: "spki" }));
10
+ originalSigningKey = process.env.URATEAM_LICENSE_SIGNING_KEY_DER_B64;
11
+ process.env.URATEAM_LICENSE_SIGNING_KEY_DER_B64 = Buffer.from(privateKey.export({ format: "der", type: "pkcs8" })).toString("base64");
12
+ });
13
+ afterEach(() => {
14
+ if (originalSigningKey === undefined) {
15
+ delete process.env.URATEAM_LICENSE_SIGNING_KEY_DER_B64;
16
+ }
17
+ else {
18
+ process.env.URATEAM_LICENSE_SIGNING_KEY_DER_B64 = originalSigningKey;
19
+ }
20
+ });
21
+ function decodeJwt(token) {
22
+ const [h, p] = token.split(".");
23
+ const fromB64Url = (s) => Buffer.from(s.replace(/-/g, "+").replace(/_/g, "/") +
24
+ "=".repeat((4 - (s.length % 4)) % 4), "base64").toString("utf-8");
25
+ return { header: JSON.parse(fromB64Url(h)), payload: JSON.parse(fromB64Url(p)) };
26
+ }
27
+ it("issues a signed JWT with the requested claims", () => {
28
+ const token = issueLicense({
29
+ customerId: "cust_acme",
30
+ tier: "enterprise",
31
+ seats: 100,
32
+ expiresAt: new Date("2027-04-13T00:00:00Z"),
33
+ });
34
+ const { header, payload } = decodeJwt(token);
35
+ expect(header).toEqual({ alg: "EdDSA", typ: "JWT" });
36
+ expect(payload.iss).toBe("urateams.com");
37
+ expect(payload.sub).toBe("cust_acme");
38
+ expect(payload.tier).toBe("enterprise");
39
+ expect(payload.seats).toBe(100);
40
+ expect(payload.exp).toBe(Math.floor(new Date("2027-04-13T00:00:00Z").getTime() / 1000));
41
+ });
42
+ it("produces a JWT whose signature verifies with the matching public key", () => {
43
+ const token = issueLicense({
44
+ customerId: "cust_test",
45
+ tier: "pro",
46
+ seats: 25,
47
+ expiresAt: new Date(Date.now() + 86_400_000),
48
+ });
49
+ const [h, p, s] = token.split(".");
50
+ const signingInput = Buffer.from(`${h}.${p}`);
51
+ const fromB64Url = (str) => Buffer.from(str.replace(/-/g, "+").replace(/_/g, "/") +
52
+ "=".repeat((4 - (str.length % 4)) % 4), "base64");
53
+ const sig = fromB64Url(s);
54
+ const pk = createPublicKey({ key: publicKeyDer, format: "der", type: "spki" });
55
+ expect(verify(null, signingInput, pk, sig)).toBe(true);
56
+ });
57
+ it("throws when URATEAM_LICENSE_SIGNING_KEY_DER_B64 is not set", () => {
58
+ delete process.env.URATEAM_LICENSE_SIGNING_KEY_DER_B64;
59
+ expect(() => issueLicense({
60
+ customerId: "cust_test",
61
+ tier: "pro",
62
+ seats: 25,
63
+ expiresAt: new Date(Date.now() + 86_400_000),
64
+ })).toThrow(/URATEAM_LICENSE_SIGNING_KEY_DER_B64/);
65
+ });
66
+ });
67
+ describe("licenseCommand action", () => {
68
+ let originalSigningKey;
69
+ beforeEach(() => {
70
+ const { privateKey } = generateKeyPairSync("ed25519");
71
+ originalSigningKey = process.env.URATEAM_LICENSE_SIGNING_KEY_DER_B64;
72
+ process.env.URATEAM_LICENSE_SIGNING_KEY_DER_B64 = Buffer.from(privateKey.export({ format: "der", type: "pkcs8" })).toString("base64");
73
+ });
74
+ afterEach(() => {
75
+ if (originalSigningKey === undefined) {
76
+ delete process.env.URATEAM_LICENSE_SIGNING_KEY_DER_B64;
77
+ }
78
+ else {
79
+ process.env.URATEAM_LICENSE_SIGNING_KEY_DER_B64 = originalSigningKey;
80
+ }
81
+ });
82
+ it("rejects --seats 0 instead of silently issuing an unlimited license", async () => {
83
+ const { licenseCommand } = await import("../commands/license.js");
84
+ licenseCommand.exitOverride(); // throw instead of process.exit on commander errors
85
+ await expect(licenseCommand.parseAsync([
86
+ "issue",
87
+ "--customer-id", "cust_test",
88
+ "--tier", "pro",
89
+ "--expires", "2027-12-31",
90
+ "--seats", "0",
91
+ ], { from: "user" })).rejects.toThrow(/--seats must be a positive integer/);
92
+ });
93
+ it("rejects an unknown --tier value", async () => {
94
+ const { licenseCommand } = await import("../commands/license.js");
95
+ licenseCommand.exitOverride();
96
+ await expect(licenseCommand.parseAsync([
97
+ "issue",
98
+ "--customer-id", "cust_test",
99
+ "--tier", "free",
100
+ "--expires", "2027-12-31",
101
+ ], { from: "user" })).rejects.toThrow(/tier must be 'pro' or 'enterprise'/);
102
+ });
103
+ it("rejects an unparseable --expires", async () => {
104
+ const { licenseCommand } = await import("../commands/license.js");
105
+ licenseCommand.exitOverride();
106
+ await expect(licenseCommand.parseAsync([
107
+ "issue",
108
+ "--customer-id", "cust_test",
109
+ "--tier", "pro",
110
+ "--expires", "not-a-date",
111
+ ], { from: "user" })).rejects.toThrow(/invalid --expires/);
112
+ });
113
+ });
114
+ //# sourceMappingURL=license-command.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"license-command.test.js","sourceRoot":"","sources":["../../src/__tests__/license-command.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=sso-bootstrap.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sso-bootstrap.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/sso-bootstrap.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,94 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
2
+ import { bootstrapSsoFromEnv } from "../sso-bootstrap.js";
3
+ const stubWorkosClient = vi.fn(async (_apiKey) => ({
4
+ getAuthorizationUrl: async () => "https://stub/auth",
5
+ authenticateWithCode: async () => ({
6
+ user: {
7
+ id: "u",
8
+ email: "u@example.com",
9
+ firstName: null,
10
+ lastName: null,
11
+ },
12
+ }),
13
+ }));
14
+ // Stub the core module so we don't actually hit WorkOS during tests.
15
+ vi.mock("@urateam/core", async () => {
16
+ const actual = await vi.importActual("@urateam/core");
17
+ return {
18
+ ...actual,
19
+ getDefaultWorkosClient: stubWorkosClient,
20
+ };
21
+ });
22
+ const FULL_ENV = {
23
+ URATEAM_SSO_ENABLED: "true",
24
+ URATEAM_WORKOS_API_KEY: "sk_test",
25
+ URATEAM_WORKOS_CLIENT_ID: "client_test",
26
+ URATEAM_WORKOS_REDIRECT_URI: "https://dash.example.com/auth/callback",
27
+ URATEAM_SSO_STATE_SECRET: "0123456789abcdef0123456789abcdef",
28
+ };
29
+ // The first dynamic import("@urateam/core") in a worker is slow (~3s) because
30
+ // it pulls in the whole core barrel. Give each test 15s of headroom.
31
+ describe("bootstrapSsoFromEnv", { timeout: 15_000 }, () => {
32
+ let exitSpy;
33
+ let errorSpy;
34
+ beforeEach(() => {
35
+ exitSpy = vi
36
+ .spyOn(process, "exit")
37
+ .mockImplementation(((_code) => {
38
+ throw new Error("__EXIT__");
39
+ }));
40
+ errorSpy = vi.spyOn(console, "error").mockImplementation(() => { });
41
+ });
42
+ afterEach(() => {
43
+ exitSpy.mockRestore();
44
+ errorSpy.mockRestore();
45
+ });
46
+ it("returns undefined when SSO is not enabled", async () => {
47
+ const result = await bootstrapSsoFromEnv({});
48
+ expect(result).toBeUndefined();
49
+ expect(exitSpy).not.toHaveBeenCalled();
50
+ });
51
+ it("returns sso config + workos client when all env vars are present", async () => {
52
+ const result = await bootstrapSsoFromEnv({ ...FULL_ENV });
53
+ expect(result).toBeDefined();
54
+ expect(result.sso.enabled).toBe(true);
55
+ expect(result.sso.workosClientId).toBe("client_test");
56
+ expect(result.sso.redirectUri).toBe("https://dash.example.com/auth/callback");
57
+ expect(result.workos).toBeDefined();
58
+ expect(typeof result.workos.getAuthorizationUrl).toBe("function");
59
+ expect(exitSpy).not.toHaveBeenCalled();
60
+ });
61
+ it("exits when URATEAM_WORKOS_API_KEY is missing", async () => {
62
+ const env = { ...FULL_ENV };
63
+ delete env.URATEAM_WORKOS_API_KEY;
64
+ await expect(bootstrapSsoFromEnv(env)).rejects.toThrow("__EXIT__");
65
+ expect(exitSpy).toHaveBeenCalledWith(1);
66
+ expect(errorSpy).toHaveBeenCalled();
67
+ const msg = errorSpy.mock.calls[0].join(" ");
68
+ expect(msg).toContain("URATEAM_WORKOS_API_KEY");
69
+ });
70
+ it("exits when URATEAM_SSO_STATE_SECRET is missing", async () => {
71
+ const env = { ...FULL_ENV };
72
+ delete env.URATEAM_SSO_STATE_SECRET;
73
+ await expect(bootstrapSsoFromEnv(env)).rejects.toThrow("__EXIT__");
74
+ expect(exitSpy).toHaveBeenCalledWith(1);
75
+ });
76
+ it("accepts optional allowedDomain", async () => {
77
+ const result = await bootstrapSsoFromEnv({
78
+ ...FULL_ENV,
79
+ URATEAM_SSO_ALLOWED_DOMAIN: "acme.com",
80
+ });
81
+ expect(result.sso.allowedDomain).toBe("acme.com");
82
+ });
83
+ it("exits with an actionable message when getDefaultWorkosClient throws LicenseRequiredError", async () => {
84
+ const { LicenseRequiredError } = await import("@urateam/core");
85
+ stubWorkosClient.mockRejectedValueOnce(new LicenseRequiredError("sso", "pro"));
86
+ await expect(bootstrapSsoFromEnv({ ...FULL_ENV })).rejects.toThrow("__EXIT__");
87
+ expect(exitSpy).toHaveBeenCalledWith(1);
88
+ const msg = errorSpy.mock.calls[0].join(" ");
89
+ expect(msg).toContain("requires an enterprise license");
90
+ expect(msg).toContain("Current tier: pro");
91
+ expect(msg).toContain("support@urateams.com");
92
+ });
93
+ });
94
+ //# sourceMappingURL=sso-bootstrap.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sso-bootstrap.test.js","sourceRoot":"","sources":["../../src/__tests__/sso-bootstrap.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=version.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"version.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/version.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,14 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { readFileSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { getPackageVersion } from "../version.js";
5
+ describe("getPackageVersion", () => {
6
+ it("returns the version from package.json", () => {
7
+ const pkg = JSON.parse(readFileSync(join(__dirname, "..", "..", "package.json"), "utf8"));
8
+ expect(getPackageVersion()).toBe(pkg.version);
9
+ });
10
+ it("returns a semver-shaped string", () => {
11
+ expect(getPackageVersion()).toMatch(/^\d+\.\d+\.\d+/);
12
+ });
13
+ });
14
+ //# sourceMappingURL=version.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"version.test.js","sourceRoot":"","sources":["../../src/__tests__/version.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAElD,QAAQ,CAAC,mBAAmB,EAAE,GAAG,EAAE;IACjC,EAAE,CAAC,uCAAuC,EAAE,GAAG,EAAE;QAC/C,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CACpB,YAAY,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,cAAc,CAAC,EAAE,MAAM,CAAC,CAC3C,CAAC;QACzB,MAAM,CAAC,iBAAiB,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IAChD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,gCAAgC,EAAE,GAAG,EAAE;QACxC,MAAM,CAAC,iBAAiB,EAAE,CAAC,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC;IACxD,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,16 @@
1
+ import { Command } from "commander";
2
+ import { type Role } from "@urateam/core";
3
+ export interface AdminDeps {
4
+ db: any;
5
+ log: (msg: string) => void;
6
+ }
7
+ export declare function runAdminList(deps: AdminDeps): Promise<void>;
8
+ export declare function runAdminGrant(args: AdminDeps & {
9
+ email: string;
10
+ newRole: Role;
11
+ }): Promise<void>;
12
+ export declare function runAdminRevoke(args: AdminDeps & {
13
+ email: string;
14
+ }): Promise<void>;
15
+ export declare const adminCommand: Command;
16
+ //# sourceMappingURL=admin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"admin.d.ts","sourceRoot":"","sources":["../../src/commands/admin.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,OAAO,EAKL,KAAK,IAAI,EACV,MAAM,eAAe,CAAC;AAEvB,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,GAAG,CAAC;IACR,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CAC5B;AAkBD,wBAAsB,YAAY,CAAC,IAAI,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAUjE;AAED,wBAAsB,aAAa,CACjC,IAAI,EAAE,SAAS,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,IAAI,CAAA;CAAE,GACjD,OAAO,CAAC,IAAI,CAAC,CAef;AAED,wBAAsB,cAAc,CAClC,IAAI,EAAE,SAAS,GAAG;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,GAClC,OAAO,CAAC,IAAI,CAAC,CAEf;AAmBD,eAAO,MAAM,YAAY,SA4DtB,CAAC"}
@@ -0,0 +1,110 @@
1
+ import { Command } from "commander";
2
+ import * as os from "node:os";
3
+ import { createDb, isFeatureLicensed, listUsers, setUserRole, } from "@urateam/core";
4
+ function requireLicensed() {
5
+ if (!isFeatureLicensed("rbac")) {
6
+ throw new Error("RBAC is an Enterprise feature. Upgrade your license to use 'ura admin' commands.");
7
+ }
8
+ }
9
+ function actorId() {
10
+ try {
11
+ return `cli:${os.userInfo().username}`;
12
+ }
13
+ catch {
14
+ return "cli:unknown";
15
+ }
16
+ }
17
+ export async function runAdminList(deps) {
18
+ requireLicensed();
19
+ const users = await listUsers(deps.db);
20
+ deps.log("EMAIL ROLE LAST_LOGIN");
21
+ for (const u of users) {
22
+ const email = u.email.padEnd(32).slice(0, 32);
23
+ const role = u.role.padEnd(10).slice(0, 10);
24
+ const ll = u.lastLoginAt ? u.lastLoginAt.toISOString() : "(never)";
25
+ deps.log(`${email} ${role} ${ll}`);
26
+ }
27
+ }
28
+ export async function runAdminGrant(args) {
29
+ requireLicensed();
30
+ const normalized = args.email.trim().toLowerCase();
31
+ const users = await listUsers(args.db);
32
+ const target = users.find((u) => u.email.toLowerCase() === normalized);
33
+ if (!target) {
34
+ throw new Error(`user not found: ${args.email}`);
35
+ }
36
+ await setUserRole(args.db, {
37
+ userId: target.id,
38
+ newRole: args.newRole,
39
+ actorUserId: actorId(),
40
+ });
41
+ args.log(`Updated ${target.email} → ${args.newRole}`);
42
+ }
43
+ export async function runAdminRevoke(args) {
44
+ await runAdminGrant({ ...args, newRole: "viewer" });
45
+ }
46
+ // ---------------- commander wiring ----------------
47
+ async function openDb() {
48
+ const conn = process.env.DATABASE_URL;
49
+ if (!conn) {
50
+ throw new Error("DATABASE_URL is not set. Set it to your urateam database (e.g. postgres://... or file:./ura.db).");
51
+ }
52
+ return createDb({ connectionString: conn });
53
+ }
54
+ function fail(err) {
55
+ console.error(err instanceof Error ? err.message : String(err));
56
+ process.exit(1);
57
+ }
58
+ export const adminCommand = new Command("admin")
59
+ .description("Manage dashboard user roles (Enterprise)")
60
+ .addCommand(new Command("list")
61
+ .description("List all dashboard users and their roles")
62
+ .action(async () => {
63
+ try {
64
+ const db = await openDb();
65
+ await runAdminList({ db, log: (s) => console.log(s) });
66
+ }
67
+ catch (err) {
68
+ fail(err);
69
+ }
70
+ }))
71
+ .addCommand(new Command("grant")
72
+ .description("Grant a role to a user (by email)")
73
+ .argument("<email>", "Target user email")
74
+ .option("--role <role>", "New role: admin | operator | viewer", "operator")
75
+ .action(async (email, opts) => {
76
+ if (opts.role !== "admin" &&
77
+ opts.role !== "operator" &&
78
+ opts.role !== "viewer") {
79
+ fail(new Error(`invalid --role: '${opts.role}'`));
80
+ }
81
+ try {
82
+ const db = await openDb();
83
+ await runAdminGrant({
84
+ db,
85
+ email,
86
+ newRole: opts.role,
87
+ log: (s) => console.log(s),
88
+ });
89
+ }
90
+ catch (err) {
91
+ fail(err);
92
+ }
93
+ }))
94
+ .addCommand(new Command("revoke")
95
+ .description("Revoke privileges — sets role to viewer")
96
+ .argument("<email>", "Target user email")
97
+ .action(async (email) => {
98
+ try {
99
+ const db = await openDb();
100
+ await runAdminRevoke({
101
+ db,
102
+ email,
103
+ log: (s) => console.log(s),
104
+ });
105
+ }
106
+ catch (err) {
107
+ fail(err);
108
+ }
109
+ }));
110
+ //# sourceMappingURL=admin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"admin.js","sourceRoot":"","sources":["../../src/commands/admin.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,EACL,QAAQ,EACR,iBAAiB,EACjB,SAAS,EACT,WAAW,GAEZ,MAAM,eAAe,CAAC;AAOvB,SAAS,eAAe;IACtB,IAAI,CAAC,iBAAiB,CAAC,MAAM,CAAC,EAAE,CAAC;QAC/B,MAAM,IAAI,KAAK,CACb,kFAAkF,CACnF,CAAC;IACJ,CAAC;AACH,CAAC;AAED,SAAS,OAAO;IACd,IAAI,CAAC;QACH,OAAO,OAAO,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE,CAAC;IACzC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,aAAa,CAAC;IACvB,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,IAAe;IAChD,eAAe,EAAE,CAAC;IAClB,MAAM,KAAK,GAAG,MAAM,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACvC,IAAI,CAAC,GAAG,CAAC,wDAAwD,CAAC,CAAC;IACnE,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;QACtB,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QAC9C,MAAM,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QAC5C,MAAM,EAAE,GAAG,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;QACnE,IAAI,CAAC,GAAG,CAAC,GAAG,KAAK,IAAI,IAAI,IAAI,EAAE,EAAE,CAAC,CAAC;IACrC,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,IAAkD;IAElD,eAAe,EAAE,CAAC;IAClB,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IACnD,MAAM,KAAK,GAAG,MAAM,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACvC,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,WAAW,EAAE,KAAK,UAAU,CAAC,CAAC;IACvE,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CAAC,mBAAmB,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC;IACnD,CAAC;IAED,MAAM,WAAW,CAAC,IAAI,CAAC,EAAE,EAAE;QACzB,MAAM,EAAE,MAAM,CAAC,EAAE;QACjB,OAAO,EAAE,IAAI,CAAC,OAAO;QACrB,WAAW,EAAE,OAAO,EAAE;KACvB,CAAC,CAAC;IACH,IAAI,CAAC,GAAG,CAAC,WAAW,MAAM,CAAC,KAAK,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC;AACxD,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,IAAmC;IAEnC,MAAM,aAAa,CAAC,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,CAAC;AACtD,CAAC;AAED,qDAAqD;AAErD,KAAK,UAAU,MAAM;IACnB,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC;IACtC,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,MAAM,IAAI,KAAK,CACb,kGAAkG,CACnG,CAAC;IACJ,CAAC;IACD,OAAO,QAAQ,CAAC,EAAE,gBAAgB,EAAE,IAAI,EAAE,CAAC,CAAC;AAC9C,CAAC;AAED,SAAS,IAAI,CAAC,GAAY;IACxB,OAAO,CAAC,KAAK,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;IAChE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAED,MAAM,CAAC,MAAM,YAAY,GAAG,IAAI,OAAO,CAAC,OAAO,CAAC;KAC7C,WAAW,CAAC,0CAA0C,CAAC;KACvD,UAAU,CACT,IAAI,OAAO,CAAC,MAAM,CAAC;KAChB,WAAW,CAAC,0CAA0C,CAAC;KACvD,MAAM,CAAC,KAAK,IAAI,EAAE;IACjB,IAAI,CAAC;QACH,MAAM,EAAE,GAAG,MAAM,MAAM,EAAE,CAAC;QAC1B,MAAM,YAAY,CAAC,EAAE,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IACzD,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,CAAC,GAAG,CAAC,CAAC;IACZ,CAAC;AACH,CAAC,CAAC,CACL;KACA,UAAU,CACT,IAAI,OAAO,CAAC,OAAO,CAAC;KACjB,WAAW,CAAC,mCAAmC,CAAC;KAChD,QAAQ,CAAC,SAAS,EAAE,mBAAmB,CAAC;KACxC,MAAM,CACL,eAAe,EACf,qCAAqC,EACrC,UAAU,CACX;KACA,MAAM,CAAC,KAAK,EAAE,KAAa,EAAE,IAAsB,EAAE,EAAE;IACtD,IACE,IAAI,CAAC,IAAI,KAAK,OAAO;QACrB,IAAI,CAAC,IAAI,KAAK,UAAU;QACxB,IAAI,CAAC,IAAI,KAAK,QAAQ,EACtB,CAAC;QACD,IAAI,CAAC,IAAI,KAAK,CAAC,oBAAoB,IAAI,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC;IACpD,CAAC;IACD,IAAI,CAAC;QACH,MAAM,EAAE,GAAG,MAAM,MAAM,EAAE,CAAC;QAC1B,MAAM,aAAa,CAAC;YAClB,EAAE;YACF,KAAK;YACL,OAAO,EAAE,IAAI,CAAC,IAAY;YAC1B,GAAG,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC;SAC3B,CAAC,CAAC;IACL,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,CAAC,GAAG,CAAC,CAAC;IACZ,CAAC;AACH,CAAC,CAAC,CACL;KACA,UAAU,CACT,IAAI,OAAO,CAAC,QAAQ,CAAC;KAClB,WAAW,CAAC,yCAAyC,CAAC;KACtD,QAAQ,CAAC,SAAS,EAAE,mBAAmB,CAAC;KACxC,MAAM,CAAC,KAAK,EAAE,KAAa,EAAE,EAAE;IAC9B,IAAI,CAAC;QACH,MAAM,EAAE,GAAG,MAAM,MAAM,EAAE,CAAC;QAC1B,MAAM,cAAc,CAAC;YACnB,EAAE;YACF,KAAK;YACL,GAAG,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC;SAC3B,CAAC,CAAC;IACL,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,CAAC,GAAG,CAAC,CAAC;IACZ,CAAC;AACH,CAAC,CAAC,CACL,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"dev.d.ts","sourceRoot":"","sources":["../../src/commands/dev.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,eAAO,MAAM,UAAU,SAiFnB,CAAC"}
1
+ {"version":3,"file":"dev.d.ts","sourceRoot":"","sources":["../../src/commands/dev.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAGpC,eAAO,MAAM,UAAU,SAkHnB,CAAC"}
@@ -1,4 +1,5 @@
1
1
  import { Command } from "commander";
2
+ import { bootstrapSsoFromEnv } from "../sso-bootstrap.js";
2
3
  export const devCommand = new Command("dev")
3
4
  .description("Start local development server (webhook + dashboard)")
4
5
  .option("--port <port>", "Webhook server port", "3000")
@@ -34,6 +35,20 @@ export const devCommand = new Command("dev")
34
35
  }
35
36
  repoConfigs[process.env.REPO_TEAM_ID] = repoEntry;
36
37
  }
38
+ // Fail fast if no repoConfigs could be built. Without this, `ura dev`
39
+ // looks healthy in logs (webhook server up, dashboard up) but every
40
+ // inbound Linear webhook fails with "no repo mapping" — usually after
41
+ // the user has already moved a real issue to Todo. The first-time-user
42
+ // setup path nearly always lands here because .urateam/.env ships with
43
+ // `REPO_URL=` and `REPO_TEAM_ID=` blank. See urateam#33.
44
+ if (Object.keys(repoConfigs).length === 0) {
45
+ console.error("Error: no repoConfigs could be built from environment variables.\n" +
46
+ "Set REPO_TEAM_ID and REPO_URL in .urateam/.env and restart.\n" +
47
+ "Example:\n" +
48
+ " REPO_TEAM_ID=<your Linear team UUID — usually the same as LINEAR_TEAM_ID>\n" +
49
+ " REPO_URL=https://github.com/org/repo\n");
50
+ process.exit(1);
51
+ }
37
52
  const config = {
38
53
  webhookSecret: process.env.LINEAR_WEBHOOK_SECRET ?? "dev-secret",
39
54
  linearApiKey: process.env.LINEAR_API_KEY ?? "",
@@ -45,12 +60,26 @@ export const devCommand = new Command("dev")
45
60
  repoCloneDir: process.env.REPO_CLONE_DIR ?? "/tmp/agent-repos",
46
61
  githubWebhookSecret: process.env.GITHUB_WEBHOOK_SECRET,
47
62
  };
63
+ // Validate SSO env vars before opening DB so misconfig fails fast.
64
+ const ssoBootstrap = await bootstrapSsoFromEnv();
48
65
  const { app, db } = await createApp(config);
66
+ try {
67
+ const { checkLicense, logAuditEvent, configLoadedEvent } = await import("@urateam/core");
68
+ const { createHash } = await import("node:crypto");
69
+ const status = checkLicense(db);
70
+ const sha = createHash("sha256").update(JSON.stringify(config.pipelineConfigs, null, 0)).digest("hex");
71
+ void logAuditEvent(db, configLoadedEvent({ path: "(env-vars)", sha256: sha, tier: status.tier }));
72
+ }
73
+ catch {
74
+ // audit must never crash startup
75
+ }
49
76
  const dashboardApp = createDashboard({
50
77
  db,
51
78
  pipelineConfigs: config.pipelineConfigs,
52
79
  repoConfigs: config.repoConfigs,
53
80
  auth: dashboardAuth,
81
+ sso: ssoBootstrap?.sso,
82
+ workos: ssoBootstrap?.workos,
54
83
  });
55
84
  const { serve } = await import("@hono/node-server");
56
85
  const port = parseInt(options.port, 10);