ras-stack 1.1.1 → 2.0.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 (45) hide show
  1. package/README.md +0 -2
  2. package/config/oxlint/domain.json +29 -0
  3. package/config/oxlint/layers.json +116 -0
  4. package/dist/auth/providers.js +1 -1
  5. package/dist/auth/providers.js.map +1 -1
  6. package/dist/email/index.d.ts +11 -0
  7. package/dist/email/index.js +16 -0
  8. package/dist/email/index.js.map +1 -1
  9. package/dist/policy/changesets.d.ts +1 -0
  10. package/dist/policy/changesets.js +37 -0
  11. package/dist/policy/changesets.js.map +1 -0
  12. package/dist/policy/cli.js +2 -1
  13. package/dist/policy/cli.js.map +1 -1
  14. package/dist/policy/index.js +5 -2
  15. package/dist/policy/index.js.map +1 -1
  16. package/dist/posthog/proxy.d.ts +1 -1
  17. package/dist/posthog/proxy.js +11 -5
  18. package/dist/posthog/proxy.js.map +1 -1
  19. package/dist/posthog/server.d.ts +6 -1
  20. package/dist/posthog/server.js +31 -0
  21. package/dist/posthog/server.js.map +1 -1
  22. package/dist/preview/dokploy.d.ts +1 -0
  23. package/dist/preview/dokploy.js +17 -0
  24. package/dist/preview/dokploy.js.map +1 -1
  25. package/dist/realtime/environment.d.ts +10 -0
  26. package/dist/realtime/environment.js +27 -0
  27. package/dist/realtime/environment.js.map +1 -0
  28. package/dist/realtime/index.d.ts +2 -0
  29. package/dist/realtime/index.js +1 -0
  30. package/dist/realtime/index.js.map +1 -1
  31. package/dist/realtime/publisher.d.ts +3 -1
  32. package/dist/realtime/publisher.js +19 -2
  33. package/dist/realtime/publisher.js.map +1 -1
  34. package/dist/runtime/index.d.ts +5 -0
  35. package/dist/runtime/index.js +15 -0
  36. package/dist/runtime/index.js.map +1 -1
  37. package/dist/server/index.d.ts +1 -1
  38. package/dist/server/index.js.map +1 -1
  39. package/dist/server/rpc.d.ts +2 -0
  40. package/dist/server/rpc.js +19 -14
  41. package/dist/server/rpc.js.map +1 -1
  42. package/dist/uploads/index.d.ts +7 -2
  43. package/dist/uploads/index.js +64 -33
  44. package/dist/uploads/index.js.map +1 -1
  45. package/package.json +6 -2
package/README.md CHANGED
@@ -29,8 +29,6 @@ An application can use one surface without adopting the others. The npm package
29
29
 
30
30
  ## The stack 🧰
31
31
 
32
- These combinations are tested here and in production. [Sealed Lists](https://github.com/richardsolomou/sealed-lists), [Praetorium](https://github.com/richardsolomou/praetorium.gg), and [STL Quest](https://github.com/richardsolomou/stl.quest) use the application and runtime pieces. [BaseKit](https://github.com/richardsolomou/basekit) and [tro.gg](https://github.com/richardsolomou/tro.gg) use only the tooling that fits their different architectures.
33
-
34
32
  | Layer | Supported technology | What `ras-stack` centralizes |
35
33
  | ------------------- | ------------------------------------------- | ----------------------------------------------------------------------------------------------------- |
36
34
  | Runtime and tooling | Node, ESM TypeScript, pnpm, Just, Oxlint | Compiler/linter bases, setup actions, and version synchronization |
@@ -0,0 +1,29 @@
1
+ {
2
+ "overrides": [
3
+ {
4
+ "files": ["src/core/**", "src/geometry/**"],
5
+ "rules": {
6
+ "no-restricted-imports": [
7
+ "error",
8
+ {
9
+ "patterns": [
10
+ {
11
+ "regex": "^(?:node:|(?:assert|async_hooks|buffer|child_process|cluster|console|constants|crypto|dgram|diagnostics_channel|dns|domain|events|fs|http|http2|https|inspector|module|net|os|path|perf_hooks|process|punycode|querystring|readline|repl|stream|string_decoder|sys|timers|tls|trace_events|tty|url|util|v8|vm|wasi|worker_threads|zlib)(?:/|$))",
12
+ "message": "Domain modules must not import Node builtins."
13
+ },
14
+ {
15
+ "regex": "^(@tanstack/|react(?:/|-|$)|@testing-library/|drizzle-orm(?:/|$)|postgres(?:/|$)|iovalkey(?:/|$)|ioredis(?:/|$)|@electric-sql/|better-auth(?:/|$)|cheerio(?:/|$)|html-to-text(?:/|$)|centrifuge(?:/|$)|posthog-(?:js|node)(?:/|$))",
16
+ "message": "Domain modules must not import framework or persistence dependencies."
17
+ },
18
+ {
19
+ "regex": "^(?:(?:\\.\\.?/)+|@/)(?:adapters|client|components|db|lib|routes|server)/",
20
+ "message": "Domain modules must not import application layers."
21
+ }
22
+ ]
23
+ }
24
+ ]
25
+ },
26
+ "excludeFiles": ["**/*.test.*", "**/*.spec.*"]
27
+ }
28
+ ]
29
+ }
@@ -0,0 +1,116 @@
1
+ {
2
+ "overrides": [
3
+ {
4
+ "files": ["src/routes/**"],
5
+ "rules": {
6
+ "no-restricted-imports": [
7
+ "error",
8
+ {
9
+ "patterns": [
10
+ {
11
+ "regex": "^(?:\\./[^.]|(?:\\.\\./)+routes/|@/routes/)",
12
+ "message": "Route modules must not import sibling route modules."
13
+ }
14
+ ]
15
+ }
16
+ ]
17
+ },
18
+ "excludeFiles": ["**/*.test.*", "**/*.spec.*"]
19
+ },
20
+ {
21
+ "files": ["src/server/**"],
22
+ "rules": {
23
+ "no-restricted-imports": [
24
+ "error",
25
+ {
26
+ "patterns": [
27
+ {
28
+ "regex": "^(?:(?:\\.\\.?/)+|@/)(?:client|routes)/",
29
+ "message": "Server modules must not import client or route modules."
30
+ }
31
+ ]
32
+ }
33
+ ]
34
+ },
35
+ "excludeFiles": ["**/*.test.*", "**/*.spec.*"]
36
+ },
37
+ {
38
+ "files": ["src/client/**"],
39
+ "rules": {
40
+ "no-restricted-imports": [
41
+ "error",
42
+ {
43
+ "patterns": [
44
+ {
45
+ "group": [
46
+ "**/adapters/**",
47
+ "**/db/**",
48
+ "**/routes/**",
49
+ "**/server/**",
50
+ "!**/server/functions",
51
+ "!**/server/functions.*",
52
+ "!**/server/functions/**",
53
+ "!**/server/fns",
54
+ "!**/server/fns.*"
55
+ ],
56
+ "message": "Client modules may import server functions, but not server implementations."
57
+ }
58
+ ]
59
+ }
60
+ ]
61
+ },
62
+ "excludeFiles": ["**/*.test.*", "**/*.spec.*"]
63
+ },
64
+ {
65
+ "files": ["src/contracts/**"],
66
+ "rules": {
67
+ "no-restricted-imports": [
68
+ "error",
69
+ {
70
+ "patterns": [
71
+ {
72
+ "regex": "^(?:(?:\\.\\.?/)+|@/)(?:adapters|client|components|db|lib|routes|server)/",
73
+ "message": "Shared contracts must not import application layers."
74
+ }
75
+ ]
76
+ }
77
+ ]
78
+ },
79
+ "excludeFiles": ["**/*.test.*", "**/*.spec.*"]
80
+ },
81
+ {
82
+ "files": ["src/adapters/**"],
83
+ "rules": {
84
+ "no-restricted-imports": [
85
+ "error",
86
+ {
87
+ "patterns": [
88
+ {
89
+ "regex": "^(?:(?:\\.\\.?/)+|@/)(?:client|db|routes|server)/",
90
+ "message": "Adapters must not import application layers."
91
+ }
92
+ ]
93
+ }
94
+ ]
95
+ },
96
+ "excludeFiles": ["**/*.test.*", "**/*.spec.*"]
97
+ },
98
+ {
99
+ "files": ["src/db/**"],
100
+ "rules": {
101
+ "no-restricted-imports": [
102
+ "error",
103
+ {
104
+ "patterns": [
105
+ {
106
+ "regex": "^(?:(?:\\.\\.?/)+|@/)(?:adapters|client|routes|server)/",
107
+ "message": "Database modules must not import application layers."
108
+ }
109
+ ]
110
+ }
111
+ ]
112
+ },
113
+ "excludeFiles": ["**/*.test.*", "**/*.spec.*"]
114
+ }
115
+ ]
116
+ }
@@ -9,7 +9,7 @@ export function providerCredentials(provider, environment = process.env, options
9
9
  const keys = providerEnvironmentKeys(provider, options.prefix);
10
10
  const clientId = environment[keys.clientId]?.trim();
11
11
  const clientSecret = environment[keys.clientSecret]?.trim();
12
- if (options.rejectPartial && Boolean(clientId) !== Boolean(clientSecret)) {
12
+ if (options.rejectPartial !== false && Boolean(clientId) !== Boolean(clientSecret)) {
13
13
  throw new Error(`${keys.clientId} and ${keys.clientSecret} must be configured together`);
14
14
  }
15
15
  return clientId && clientSecret ? { clientId, clientSecret } : undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"providers.js","sourceRoot":"","sources":["../../src/auth/providers.ts"],"names":[],"mappings":"AAGA,SAAS,uBAAuB,CAAC,QAAgB,EAAE,MAAM,GAAG,EAAE;IAC5D,MAAM,IAAI,GAAG,QAAQ,CAAC,WAAW,EAAE,CAAC,UAAU,CAAC,GAAG,EAAE,GAAG,CAAC,CAAA;IACxD,OAAO,EAAE,QAAQ,EAAE,GAAG,MAAM,GAAG,IAAI,YAAY,EAAE,YAAY,EAAE,GAAG,MAAM,GAAG,IAAI,gBAAgB,EAAE,CAAA;AACnG,CAAC;AAED,MAAM,UAAU,mBAAmB,CACjC,SAA8B,EAC9B,WAAW,GAAsB,OAAO,CAAC,GAAG,EAC5C,OAAO,GAA+B,EAAE;IAExC,OAAO,SAAS,CAAC,MAAM,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,OAAO,CAAC,mBAAmB,CAAC,QAAQ,EAAE,WAAW,EAAE,OAAO,CAAC,CAAC,CAAC,CAAA;AACrG,CAAC;AAED,MAAM,UAAU,mBAAmB,CACjC,QAAgB,EAChB,WAAW,GAAsB,OAAO,CAAC,GAAG,EAC5C,OAAO,GAA+B,EAAE;IAExC,MAAM,IAAI,GAAG,uBAAuB,CAAC,QAAQ,EAAE,OAAO,CAAC,MAAM,CAAC,CAAA;IAC9D,MAAM,QAAQ,GAAG,WAAW,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,CAAA;IACnD,MAAM,YAAY,GAAG,WAAW,CAAC,IAAI,CAAC,YAAY,CAAC,EAAE,IAAI,EAAE,CAAA;IAC3D,IAAI,OAAO,CAAC,aAAa,IAAI,OAAO,CAAC,QAAQ,CAAC,KAAK,OAAO,CAAC,YAAY,CAAC,EAAE,CAAC;QACzE,MAAM,IAAI,KAAK,CAAC,GAAG,IAAI,CAAC,QAAQ,QAAQ,IAAI,CAAC,YAAY,8BAA8B,CAAC,CAAA;IAC1F,CAAC;IACD,OAAO,QAAQ,IAAI,YAAY,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,YAAY,EAAE,CAAC,CAAC,CAAC,SAAS,CAAA;AAC1E,CAAC;AAED,MAAM,UAAU,yBAAyB,CACvC,SAA8B,EAC9B,WAAW,GAAsB,OAAO,CAAC,GAAG,EAC5C,kBAAkB,GAA+B,EAAE;IAEnD,MAAM,eAAe,GAAmD,EAAE,CAAA;IAC1E,KAAK,MAAM,QAAQ,IAAI,SAAS,EAAE,CAAC;QACjC,MAAM,WAAW,GAAG,mBAAmB,CAAC,QAAQ,EAAE,WAAW,EAAE,kBAAkB,CAAC,CAAA;QAClF,IAAI,WAAW;YAAE,eAAe,CAAC,QAAQ,CAAC,GAAG,WAAW,CAAA;IAC1D,CAAC;IACD,OAAO,eAAe,CAAA;AACxB,CAAC","sourcesContent":["export type ProviderCredentials = { clientId: string; clientSecret: string }\nexport type ProviderEnvironmentOptions = { prefix?: string; rejectPartial?: boolean }\n\nfunction providerEnvironmentKeys(provider: string, prefix = '') {\n const name = provider.toUpperCase().replaceAll('-', '_')\n return { clientId: `${prefix}${name}_CLIENT_ID`, clientSecret: `${prefix}${name}_CLIENT_SECRET` }\n}\n\nexport function configuredProviders<const Provider extends string>(\n providers: readonly Provider[],\n environment: NodeJS.ProcessEnv = process.env,\n options: ProviderEnvironmentOptions = {},\n) {\n return providers.filter((provider) => Boolean(providerCredentials(provider, environment, options)))\n}\n\nexport function providerCredentials(\n provider: string,\n environment: NodeJS.ProcessEnv = process.env,\n options: ProviderEnvironmentOptions = {},\n): ProviderCredentials | undefined {\n const keys = providerEnvironmentKeys(provider, options.prefix)\n const clientId = environment[keys.clientId]?.trim()\n const clientSecret = environment[keys.clientSecret]?.trim()\n if (options.rejectPartial && Boolean(clientId) !== Boolean(clientSecret)) {\n throw new Error(`${keys.clientId} and ${keys.clientSecret} must be configured together`)\n }\n return clientId && clientSecret ? { clientId, clientSecret } : undefined\n}\n\nexport function configuredProviderOptions<const Provider extends string>(\n providers: readonly Provider[],\n environment: NodeJS.ProcessEnv = process.env,\n environmentOptions: ProviderEnvironmentOptions = {},\n): Partial<Record<Provider, ProviderCredentials>> {\n const providerOptions: Partial<Record<Provider, ProviderCredentials>> = {}\n for (const provider of providers) {\n const credentials = providerCredentials(provider, environment, environmentOptions)\n if (credentials) providerOptions[provider] = credentials\n }\n return providerOptions\n}\n"]}
1
+ {"version":3,"file":"providers.js","sourceRoot":"","sources":["../../src/auth/providers.ts"],"names":[],"mappings":"AAGA,SAAS,uBAAuB,CAAC,QAAgB,EAAE,MAAM,GAAG,EAAE;IAC5D,MAAM,IAAI,GAAG,QAAQ,CAAC,WAAW,EAAE,CAAC,UAAU,CAAC,GAAG,EAAE,GAAG,CAAC,CAAA;IACxD,OAAO,EAAE,QAAQ,EAAE,GAAG,MAAM,GAAG,IAAI,YAAY,EAAE,YAAY,EAAE,GAAG,MAAM,GAAG,IAAI,gBAAgB,EAAE,CAAA;AACnG,CAAC;AAED,MAAM,UAAU,mBAAmB,CACjC,SAA8B,EAC9B,WAAW,GAAsB,OAAO,CAAC,GAAG,EAC5C,OAAO,GAA+B,EAAE;IAExC,OAAO,SAAS,CAAC,MAAM,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,OAAO,CAAC,mBAAmB,CAAC,QAAQ,EAAE,WAAW,EAAE,OAAO,CAAC,CAAC,CAAC,CAAA;AACrG,CAAC;AAED,MAAM,UAAU,mBAAmB,CACjC,QAAgB,EAChB,WAAW,GAAsB,OAAO,CAAC,GAAG,EAC5C,OAAO,GAA+B,EAAE;IAExC,MAAM,IAAI,GAAG,uBAAuB,CAAC,QAAQ,EAAE,OAAO,CAAC,MAAM,CAAC,CAAA;IAC9D,MAAM,QAAQ,GAAG,WAAW,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,CAAA;IACnD,MAAM,YAAY,GAAG,WAAW,CAAC,IAAI,CAAC,YAAY,CAAC,EAAE,IAAI,EAAE,CAAA;IAC3D,IAAI,OAAO,CAAC,aAAa,KAAK,KAAK,IAAI,OAAO,CAAC,QAAQ,CAAC,KAAK,OAAO,CAAC,YAAY,CAAC,EAAE,CAAC;QACnF,MAAM,IAAI,KAAK,CAAC,GAAG,IAAI,CAAC,QAAQ,QAAQ,IAAI,CAAC,YAAY,8BAA8B,CAAC,CAAA;IAC1F,CAAC;IACD,OAAO,QAAQ,IAAI,YAAY,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,YAAY,EAAE,CAAC,CAAC,CAAC,SAAS,CAAA;AAC1E,CAAC;AAED,MAAM,UAAU,yBAAyB,CACvC,SAA8B,EAC9B,WAAW,GAAsB,OAAO,CAAC,GAAG,EAC5C,kBAAkB,GAA+B,EAAE;IAEnD,MAAM,eAAe,GAAmD,EAAE,CAAA;IAC1E,KAAK,MAAM,QAAQ,IAAI,SAAS,EAAE,CAAC;QACjC,MAAM,WAAW,GAAG,mBAAmB,CAAC,QAAQ,EAAE,WAAW,EAAE,kBAAkB,CAAC,CAAA;QAClF,IAAI,WAAW;YAAE,eAAe,CAAC,QAAQ,CAAC,GAAG,WAAW,CAAA;IAC1D,CAAC;IACD,OAAO,eAAe,CAAA;AACxB,CAAC","sourcesContent":["export type ProviderCredentials = { clientId: string; clientSecret: string }\nexport type ProviderEnvironmentOptions = { prefix?: string; rejectPartial?: boolean }\n\nfunction providerEnvironmentKeys(provider: string, prefix = '') {\n const name = provider.toUpperCase().replaceAll('-', '_')\n return { clientId: `${prefix}${name}_CLIENT_ID`, clientSecret: `${prefix}${name}_CLIENT_SECRET` }\n}\n\nexport function configuredProviders<const Provider extends string>(\n providers: readonly Provider[],\n environment: NodeJS.ProcessEnv = process.env,\n options: ProviderEnvironmentOptions = {},\n) {\n return providers.filter((provider) => Boolean(providerCredentials(provider, environment, options)))\n}\n\nexport function providerCredentials(\n provider: string,\n environment: NodeJS.ProcessEnv = process.env,\n options: ProviderEnvironmentOptions = {},\n): ProviderCredentials | undefined {\n const keys = providerEnvironmentKeys(provider, options.prefix)\n const clientId = environment[keys.clientId]?.trim()\n const clientSecret = environment[keys.clientSecret]?.trim()\n if (options.rejectPartial !== false && Boolean(clientId) !== Boolean(clientSecret)) {\n throw new Error(`${keys.clientId} and ${keys.clientSecret} must be configured together`)\n }\n return clientId && clientSecret ? { clientId, clientSecret } : undefined\n}\n\nexport function configuredProviderOptions<const Provider extends string>(\n providers: readonly Provider[],\n environment: NodeJS.ProcessEnv = process.env,\n environmentOptions: ProviderEnvironmentOptions = {},\n): Partial<Record<Provider, ProviderCredentials>> {\n const providerOptions: Partial<Record<Provider, ProviderCredentials>> = {}\n for (const provider of providers) {\n const credentials = providerCredentials(provider, environment, environmentOptions)\n if (credentials) providerOptions[provider] = credentials\n }\n return providerOptions\n}\n"]}
@@ -47,3 +47,14 @@ export declare function createAuthEmailHandler<User extends {
47
47
  export declare function smtpConfigFromEnvironment(environment?: NodeJS.ProcessEnv, keys?: SmtpEnvironmentOptions): SmtpConfig | undefined;
48
48
  export declare function createSmtpDelivery(config: SmtpConfig): EmailDelivery;
49
49
  export declare function createSmtpTransport(config: SmtpConfig): nodemailer.Transporter<import("nodemailer/lib/smtp-transport/index.js").SentMessageInfo, import("nodemailer/lib/smtp-transport/index.js").Options>;
50
+ export type StandardAuthEmailOptions = {
51
+ productName: string;
52
+ };
53
+ export declare function standardAuthEmails<User extends {
54
+ email: string;
55
+ } = {
56
+ email: string;
57
+ }>(delivery: EmailDelivery, options: StandardAuthEmailOptions): {
58
+ sendResetPassword: (input: AuthEmailInput<User>, request?: Request) => Promise<void>;
59
+ sendVerificationEmail: (input: AuthEmailInput<User>, request?: Request) => Promise<void>;
60
+ };
@@ -44,4 +44,20 @@ export function createSmtpTransport(config) {
44
44
  auth: config.user ? { user: config.user, pass: config.password } : undefined,
45
45
  });
46
46
  }
47
+ export function standardAuthEmails(delivery, options) {
48
+ const expiry = 'This link expires in one hour.';
49
+ const message = (subject, label) => ({ user, url }) => ({
50
+ to: user.email,
51
+ subject,
52
+ text: `${subject} using this link: ${url}\n\n${expiry}`,
53
+ html: `<p>${escapeHtml(subject)} using the link below.</p><p><a href="${escapeHtml(url)}">${label}</a></p><p>${expiry}</p>`,
54
+ });
55
+ return {
56
+ sendResetPassword: createAuthEmailHandler(delivery, message(`Reset your ${options.productName} password`, 'Reset password')),
57
+ sendVerificationEmail: createAuthEmailHandler(delivery, message(`Verify your ${options.productName} email address`, 'Verify email address')),
58
+ };
59
+ }
60
+ function escapeHtml(value) {
61
+ return value.replaceAll('&', '&amp;').replaceAll('<', '&lt;').replaceAll('>', '&gt;').replaceAll('"', '&quot;');
62
+ }
47
63
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/email/index.ts"],"names":[],"mappings":"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","sourcesContent":["import nodemailer from 'nodemailer'\n\nexport type EmailMessage = { to: string; subject: string; text: string; html?: string }\n\nexport type SmtpConfig = {\n from: string\n host: string\n port: number\n secure: boolean\n user?: string\n password?: string\n}\n\nexport type SmtpEnvironmentOptions = {\n host?: string\n port?: string\n secure?: string\n user?: string\n password?: string\n from?: string\n}\n\nexport type EmailDelivery = {\n send(message: EmailMessage): Promise<void>\n verify(): Promise<void>\n}\n\nexport type AuthEmailInput<User extends { email: string } = { email: string }> = {\n user: User\n url: string\n token: string\n}\n\nexport type AuthEmailMessageFactory<User extends { email: string } = { email: string }> = (\n input: AuthEmailInput<User>,\n request?: Request,\n) => EmailMessage | Promise<EmailMessage>\n\nexport function createAuthEmailHandler<User extends { email: string } = { email: string }>(\n delivery: EmailDelivery,\n message: AuthEmailMessageFactory<User>,\n) {\n return async (input: AuthEmailInput<User>, request?: Request) => delivery.send(await message(input, request))\n}\n\nexport function smtpConfigFromEnvironment(\n environment: NodeJS.ProcessEnv = process.env,\n keys: SmtpEnvironmentOptions = {},\n): SmtpConfig | undefined {\n const host = environment[keys.host ?? 'SMTP_HOST']?.trim()\n const from = environment[keys.from ?? 'EMAIL_FROM']?.trim()\n if (!host && !from) return undefined\n if (!host) throw new Error(`${keys.host ?? 'SMTP_HOST'} is required for SMTP email`)\n if (!from) throw new Error(`${keys.from ?? 'EMAIL_FROM'} is required for SMTP email`)\n\n const portKey = keys.port ?? 'SMTP_PORT'\n const port = Number(environment[portKey] ?? 587)\n if (!Number.isInteger(port) || port < 1 || port > 65_535) throw new Error(`${portKey} must be a valid TCP port`)\n\n const userKey = keys.user ?? 'SMTP_USER'\n const passwordKey = keys.password ?? 'SMTP_PASSWORD'\n const user = environment[userKey]?.trim()\n const password = environment[passwordKey]\n if (Boolean(user) !== Boolean(password)) throw new Error(`${userKey} and ${passwordKey} must be configured together`)\n\n return {\n from,\n host,\n port,\n secure: environment[keys.secure ?? 'SMTP_SECURE']?.trim().toLowerCase() === 'true',\n ...(user && password ? { user, password } : {}),\n }\n}\n\nexport function createSmtpDelivery(config: SmtpConfig): EmailDelivery {\n const transport = createSmtpTransport(config)\n return {\n send: async (message) => transport.sendMail({ from: config.from, ...message }).then(() => undefined),\n verify: async () => transport.verify().then(() => undefined),\n }\n}\n\nexport function createSmtpTransport(config: SmtpConfig) {\n return nodemailer.createTransport({\n host: config.host,\n port: config.port,\n secure: config.secure,\n auth: config.user ? { user: config.user, pass: config.password } : undefined,\n })\n}\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/email/index.ts"],"names":[],"mappings":"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","sourcesContent":["import nodemailer from 'nodemailer'\n\nexport type EmailMessage = { to: string; subject: string; text: string; html?: string }\n\nexport type SmtpConfig = {\n from: string\n host: string\n port: number\n secure: boolean\n user?: string\n password?: string\n}\n\nexport type SmtpEnvironmentOptions = {\n host?: string\n port?: string\n secure?: string\n user?: string\n password?: string\n from?: string\n}\n\nexport type EmailDelivery = {\n send(message: EmailMessage): Promise<void>\n verify(): Promise<void>\n}\n\nexport type AuthEmailInput<User extends { email: string } = { email: string }> = {\n user: User\n url: string\n token: string\n}\n\nexport type AuthEmailMessageFactory<User extends { email: string } = { email: string }> = (\n input: AuthEmailInput<User>,\n request?: Request,\n) => EmailMessage | Promise<EmailMessage>\n\nexport function createAuthEmailHandler<User extends { email: string } = { email: string }>(\n delivery: EmailDelivery,\n message: AuthEmailMessageFactory<User>,\n) {\n return async (input: AuthEmailInput<User>, request?: Request) => delivery.send(await message(input, request))\n}\n\nexport function smtpConfigFromEnvironment(\n environment: NodeJS.ProcessEnv = process.env,\n keys: SmtpEnvironmentOptions = {},\n): SmtpConfig | undefined {\n const host = environment[keys.host ?? 'SMTP_HOST']?.trim()\n const from = environment[keys.from ?? 'EMAIL_FROM']?.trim()\n if (!host && !from) return undefined\n if (!host) throw new Error(`${keys.host ?? 'SMTP_HOST'} is required for SMTP email`)\n if (!from) throw new Error(`${keys.from ?? 'EMAIL_FROM'} is required for SMTP email`)\n\n const portKey = keys.port ?? 'SMTP_PORT'\n const port = Number(environment[portKey] ?? 587)\n if (!Number.isInteger(port) || port < 1 || port > 65_535) throw new Error(`${portKey} must be a valid TCP port`)\n\n const userKey = keys.user ?? 'SMTP_USER'\n const passwordKey = keys.password ?? 'SMTP_PASSWORD'\n const user = environment[userKey]?.trim()\n const password = environment[passwordKey]\n if (Boolean(user) !== Boolean(password)) throw new Error(`${userKey} and ${passwordKey} must be configured together`)\n\n return {\n from,\n host,\n port,\n secure: environment[keys.secure ?? 'SMTP_SECURE']?.trim().toLowerCase() === 'true',\n ...(user && password ? { user, password } : {}),\n }\n}\n\nexport function createSmtpDelivery(config: SmtpConfig): EmailDelivery {\n const transport = createSmtpTransport(config)\n return {\n send: async (message) => transport.sendMail({ from: config.from, ...message }).then(() => undefined),\n verify: async () => transport.verify().then(() => undefined),\n }\n}\n\nexport function createSmtpTransport(config: SmtpConfig) {\n return nodemailer.createTransport({\n host: config.host,\n port: config.port,\n secure: config.secure,\n auth: config.user ? { user: config.user, pass: config.password } : undefined,\n })\n}\n\nexport type StandardAuthEmailOptions = { productName: string }\n\nexport function standardAuthEmails<User extends { email: string } = { email: string }>(\n delivery: EmailDelivery,\n options: StandardAuthEmailOptions,\n) {\n const expiry = 'This link expires in one hour.'\n const message =\n (subject: string, label: string): AuthEmailMessageFactory<User> =>\n ({ user, url }) => ({\n to: user.email,\n subject,\n text: `${subject} using this link: ${url}\\n\\n${expiry}`,\n html: `<p>${escapeHtml(subject)} using the link below.</p><p><a href=\"${escapeHtml(url)}\">${label}</a></p><p>${expiry}</p>`,\n })\n return {\n sendResetPassword: createAuthEmailHandler(delivery, message(`Reset your ${options.productName} password`, 'Reset password')),\n sendVerificationEmail: createAuthEmailHandler(\n delivery,\n message(`Verify your ${options.productName} email address`, 'Verify email address'),\n ),\n }\n}\n\nfunction escapeHtml(value: string) {\n return value.replaceAll('&', '&amp;').replaceAll('<', '&lt;').replaceAll('>', '&gt;').replaceAll('\"', '&quot;')\n}\n"]}
@@ -0,0 +1 @@
1
+ export declare function checkChangesets(root: string): Promise<string[]>;
@@ -0,0 +1,37 @@
1
+ import { parseChangesetFile } from '@changesets/parse';
2
+ import { getPackages } from '@manypkg/get-packages';
3
+ import { readdir, readFile } from 'node:fs/promises';
4
+ import { join } from 'node:path';
5
+ export async function checkChangesets(root) {
6
+ const files = await readdir(join(root, '.changeset')).catch((error) => {
7
+ if (error.code === 'ENOENT')
8
+ return [];
9
+ throw error;
10
+ });
11
+ const changesets = files.filter((file) => file.endsWith('.md') && file !== 'README.md').toSorted();
12
+ if (changesets.length === 0)
13
+ return [];
14
+ const { packages } = await getPackages(root);
15
+ const names = new Set(packages.map(({ packageJson }) => packageJson.name));
16
+ const errors = [];
17
+ for (const file of changesets) {
18
+ const path = `.changeset/${file}`;
19
+ // Read serially so a large changeset directory does not open every file at once.
20
+ // oxlint-disable-next-line no-await-in-loop
21
+ const contents = await readFile(join(root, path), 'utf8');
22
+ let releases;
23
+ try {
24
+ releases = parseChangesetFile(contents).releases;
25
+ }
26
+ catch {
27
+ errors.push(`${path}: missing or invalid frontmatter`);
28
+ continue;
29
+ }
30
+ for (const { name } of releases) {
31
+ if (!names.has(name))
32
+ errors.push(`${path}: unknown package ${JSON.stringify(name)}`);
33
+ }
34
+ }
35
+ return errors;
36
+ }
37
+ //# sourceMappingURL=changesets.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"changesets.js","sourceRoot":"","sources":["../../src/policy/changesets.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAA;AACtD,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AACnD,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AACpD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAEhC,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,IAAY;IAChD,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE,YAAY,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,KAA4B,EAAE,EAAE;QAC3F,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ;YAAE,OAAO,EAAE,CAAA;QACtC,MAAM,KAAK,CAAA;IACb,CAAC,CAAC,CAAA;IACF,MAAM,UAAU,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,IAAI,KAAK,WAAW,CAAC,CAAC,QAAQ,EAAE,CAAA;IAClG,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAA;IACtC,MAAM,EAAE,QAAQ,EAAE,GAAG,MAAM,WAAW,CAAC,IAAI,CAAC,CAAA;IAC5C,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,EAAE,WAAW,EAAE,EAAE,EAAE,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAA;IAC1E,MAAM,MAAM,GAAa,EAAE,CAAA;IAC3B,KAAK,MAAM,IAAI,IAAI,UAAU,EAAE,CAAC;QAC9B,MAAM,IAAI,GAAG,cAAc,IAAI,EAAE,CAAA;QACjC,iFAAiF;QACjF,4CAA4C;QAC5C,MAAM,QAAQ,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,CAAA;QACzD,IAAI,QAAQ,CAAA;QACZ,IAAI,CAAC;YACH,QAAQ,GAAG,kBAAkB,CAAC,QAAQ,CAAC,CAAC,QAAQ,CAAA;QAClD,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,CAAC,IAAI,CAAC,GAAG,IAAI,kCAAkC,CAAC,CAAA;YACtD,SAAQ;QACV,CAAC;QACD,KAAK,MAAM,EAAE,IAAI,EAAE,IAAI,QAAQ,EAAE,CAAC;YAChC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC;gBAAE,MAAM,CAAC,IAAI,CAAC,GAAG,IAAI,qBAAqB,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;QACvF,CAAC;IACH,CAAC;IACD,OAAO,MAAM,CAAA;AACf,CAAC","sourcesContent":["import { parseChangesetFile } from '@changesets/parse'\nimport { getPackages } from '@manypkg/get-packages'\nimport { readdir, readFile } from 'node:fs/promises'\nimport { join } from 'node:path'\n\nexport async function checkChangesets(root: string): Promise<string[]> {\n const files = await readdir(join(root, '.changeset')).catch((error: NodeJS.ErrnoException) => {\n if (error.code === 'ENOENT') return []\n throw error\n })\n const changesets = files.filter((file) => file.endsWith('.md') && file !== 'README.md').toSorted()\n if (changesets.length === 0) return []\n const { packages } = await getPackages(root)\n const names = new Set(packages.map(({ packageJson }) => packageJson.name))\n const errors: string[] = []\n for (const file of changesets) {\n const path = `.changeset/${file}`\n // Read serially so a large changeset directory does not open every file at once.\n // oxlint-disable-next-line no-await-in-loop\n const contents = await readFile(join(root, path), 'utf8')\n let releases\n try {\n releases = parseChangesetFile(contents).releases\n } catch {\n errors.push(`${path}: missing or invalid frontmatter`)\n continue\n }\n for (const { name } of releases) {\n if (!names.has(name)) errors.push(`${path}: unknown package ${JSON.stringify(name)}`)\n }\n }\n return errors\n}\n"]}
@@ -11,7 +11,8 @@ export async function runPolicyCli(arguments_) {
11
11
  if (command === 'check' && changed.length > 0) {
12
12
  for (const message of changed)
13
13
  console.error(message);
14
- console.error('run ras policy sync and commit the result');
14
+ if (changed.some((message) => message.startsWith('policy drift: ')))
15
+ console.error('run ras policy sync and commit the result');
15
16
  process.exitCode = 1;
16
17
  }
17
18
  else if (command === 'sync') {
@@ -1 +1 @@
1
- {"version":3,"file":"cli.js","sourceRoot":"","sources":["../../src/policy/cli.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAA;AAExE,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,UAAoB;IACrD,MAAM,CAAC,OAAO,EAAE,KAAK,CAAC,GAAG,UAAU,CAAA;IACnC,qHAAqH;IACrH,IAAI,CAAC,OAAO,KAAK,OAAO,IAAI,OAAO,KAAK,MAAM,CAAC,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QACvE,OAAO,CAAC,KAAK,CAAC,gCAAgC,CAAC,CAAA;QAC/C,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAA;QACpB,OAAM;IACR,CAAC;IAED,MAAM,OAAO,GAAG,OAAO,KAAK,OAAO,CAAC,CAAC,CAAC,MAAM,qBAAqB,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,oBAAoB,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,OAAO,CAAC,CAAA;IACrI,IAAI,OAAO,KAAK,OAAO,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC9C,KAAK,MAAM,OAAO,IAAI,OAAO;YAAE,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAAA;QACrD,OAAO,CAAC,KAAK,CAAC,2CAA2C,CAAC,CAAA;QAC1D,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAA;IACtB,CAAC;SAAM,IAAI,OAAO,KAAK,MAAM,EAAE,CAAC;QAC9B,KAAK,MAAM,IAAI,IAAI,OAAO;YAAE,OAAO,CAAC,GAAG,CAAC,WAAW,IAAI,EAAE,CAAC,CAAA;IAC5D,CAAC;AACH,CAAC","sourcesContent":["import { checkRepositoryPolicy, syncRepositoryPolicy } from './index.js'\n\nexport async function runPolicyCli(arguments_: string[]): Promise<void> {\n const [command, extra] = arguments_\n // `adoption` used to be a second argument. Failing beats quietly running a repository sync somebody did not ask for.\n if ((command !== 'check' && command !== 'sync') || extra !== undefined) {\n console.error('usage: ras policy <check|sync>')\n process.exitCode = 2\n return\n }\n\n const changed = command === 'check' ? await checkRepositoryPolicy(process.cwd()) : await syncRepositoryPolicy(process.cwd(), 'write')\n if (command === 'check' && changed.length > 0) {\n for (const message of changed) console.error(message)\n console.error('run ras policy sync and commit the result')\n process.exitCode = 1\n } else if (command === 'sync') {\n for (const path of changed) console.log(`updated ${path}`)\n }\n}\n"]}
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../../src/policy/cli.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAA;AAExE,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,UAAoB;IACrD,MAAM,CAAC,OAAO,EAAE,KAAK,CAAC,GAAG,UAAU,CAAA;IACnC,qHAAqH;IACrH,IAAI,CAAC,OAAO,KAAK,OAAO,IAAI,OAAO,KAAK,MAAM,CAAC,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QACvE,OAAO,CAAC,KAAK,CAAC,gCAAgC,CAAC,CAAA;QAC/C,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAA;QACpB,OAAM;IACR,CAAC;IAED,MAAM,OAAO,GAAG,OAAO,KAAK,OAAO,CAAC,CAAC,CAAC,MAAM,qBAAqB,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,oBAAoB,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,OAAO,CAAC,CAAA;IACrI,IAAI,OAAO,KAAK,OAAO,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC9C,KAAK,MAAM,OAAO,IAAI,OAAO;YAAE,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAAA;QACrD,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,UAAU,CAAC,gBAAgB,CAAC,CAAC;YAAE,OAAO,CAAC,KAAK,CAAC,2CAA2C,CAAC,CAAA;QAC/H,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAA;IACtB,CAAC;SAAM,IAAI,OAAO,KAAK,MAAM,EAAE,CAAC;QAC9B,KAAK,MAAM,IAAI,IAAI,OAAO;YAAE,OAAO,CAAC,GAAG,CAAC,WAAW,IAAI,EAAE,CAAC,CAAA;IAC5D,CAAC;AACH,CAAC","sourcesContent":["import { checkRepositoryPolicy, syncRepositoryPolicy } from './index.js'\n\nexport async function runPolicyCli(arguments_: string[]): Promise<void> {\n const [command, extra] = arguments_\n // `adoption` used to be a second argument. Failing beats quietly running a repository sync somebody did not ask for.\n if ((command !== 'check' && command !== 'sync') || extra !== undefined) {\n console.error('usage: ras policy <check|sync>')\n process.exitCode = 2\n return\n }\n\n const changed = command === 'check' ? await checkRepositoryPolicy(process.cwd()) : await syncRepositoryPolicy(process.cwd(), 'write')\n if (command === 'check' && changed.length > 0) {\n for (const message of changed) console.error(message)\n if (changed.some((message) => message.startsWith('policy drift: '))) console.error('run ras policy sync and commit the result')\n process.exitCode = 1\n } else if (command === 'sync') {\n for (const path of changed) console.log(`updated ${path}`)\n }\n}\n"]}
@@ -1,13 +1,15 @@
1
1
  import { mkdir, readFile, writeFile } from 'node:fs/promises';
2
2
  import { dirname, join } from 'node:path';
3
3
  import { parseDocument, stringify } from 'yaml';
4
+ import { checkChangesets } from './changesets.js';
4
5
  const changesetsPolicy = {
5
6
  $schema: 'https://unpkg.com/@changesets/config@3.1.2/schema.json',
6
7
  changelog: '@changesets/cli/changelog',
7
8
  commit: false,
8
9
  fixed: [],
9
10
  linked: [],
10
- access: 'public',
11
+ access: 'restricted',
12
+ privatePackages: { version: true, tag: true },
11
13
  baseBranch: 'main',
12
14
  updateInternalDependencies: 'patch',
13
15
  ignore: [],
@@ -70,8 +72,9 @@ export async function syncRepositoryPolicy(root, mode) {
70
72
  return compared.filter((path) => path !== undefined);
71
73
  }
72
74
  export async function checkRepositoryPolicy(root) {
75
+ const config = repositoryPolicy(await readFile(join(root, 'ras-stack.policy.json'), 'utf8'));
73
76
  const files = await syncRepositoryPolicy(root, 'check');
74
- return files.map((path) => `policy drift: ${path}`);
77
+ return [...files.map((path) => `policy drift: ${path}`), ...(config.changesets ? await checkChangesets(root) : [])];
75
78
  }
76
79
  export async function renderedPolicyFiles(root, config) {
77
80
  const files = new Map();
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/policy/index.ts"],"names":[],"mappings":"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","sourcesContent":["import { mkdir, readFile, writeFile } from 'node:fs/promises'\nimport { dirname, join } from 'node:path'\nimport { parseDocument, stringify } from 'yaml'\n\nconst changesetsPolicy = {\n $schema: 'https://unpkg.com/@changesets/config@3.1.2/schema.json',\n changelog: '@changesets/cli/changelog',\n commit: false,\n fixed: [],\n linked: [],\n access: 'public',\n baseBranch: 'main',\n updateInternalDependencies: 'patch',\n ignore: [],\n}\n\nconst dependabotPolicy = {\n version: 2,\n updates: [\n {\n 'package-ecosystem': 'npm',\n directory: '/',\n schedule: { interval: 'weekly' },\n cooldown: { 'default-days': 7 },\n allow: [\n {\n 'dependency-name': '*',\n 'update-types': ['version-update:semver-minor', 'version-update:semver-patch'],\n },\n ],\n groups: {\n 'patch-updates': {\n 'applies-to': 'version-updates',\n patterns: ['*'],\n 'update-types': ['patch'],\n },\n 'minor-updates': {\n 'applies-to': 'version-updates',\n patterns: ['*'],\n 'update-types': ['minor'],\n },\n },\n },\n {\n 'package-ecosystem': 'github-actions',\n directory: '/',\n schedule: { interval: 'weekly' },\n cooldown: { 'default-days': 7 },\n ignore: [\n {\n 'dependency-name': 'richardsolomou/ras-stack*',\n 'update-types': ['version-update:semver-major'],\n },\n ],\n },\n ],\n}\n\ntype PolicySelection = boolean | { overrides?: Record<string, unknown> }\n\nexport type RepositoryPolicy = {\n changesets?: PolicySelection\n dependabot?: PolicySelection\n pnpm?: false | { minimumReleaseAge?: number }\n}\n\nexport async function syncRepositoryPolicy(root: string, mode: 'check' | 'write') {\n const config = repositoryPolicy(await readFile(join(root, 'ras-stack.policy.json'), 'utf8'))\n const files = await renderedPolicyFiles(root, config)\n const compared = await Promise.all(\n [...files].map(async ([path, expected]) => {\n const absolute = join(root, path)\n const actual = await readFile(absolute, 'utf8').catch(() => undefined)\n if (actual === expected) return undefined\n if (mode === 'write') {\n await mkdir(dirname(absolute), { recursive: true })\n await writeFile(absolute, expected)\n }\n return path\n }),\n )\n return compared.filter((path) => path !== undefined)\n}\n\nexport async function checkRepositoryPolicy(root: string) {\n const files = await syncRepositoryPolicy(root, 'check')\n return files.map((path) => `policy drift: ${path}`)\n}\n\nexport async function renderedPolicyFiles(root: string, config: RepositoryPolicy) {\n const files = new Map<string, string>()\n if (config.changesets) {\n files.set('.changeset/config.json', `${JSON.stringify(selectedPolicy(changesetsPolicy, config.changesets), null, 2)}\\n`)\n }\n if (config.dependabot) {\n files.set('.github/dependabot.yml', stringify(selectedPolicy(dependabotPolicy, config.dependabot), { lineWidth: 0, singleQuote: true }))\n }\n if (config.pnpm) {\n const path = join(root, 'pnpm-workspace.yaml')\n const document = parseDocument(await readFile(path, 'utf8').catch(() => ''))\n document.set('minimumReleaseAge', config.pnpm.minimumReleaseAge ?? 10_080)\n files.set('pnpm-workspace.yaml', document.toString({ lineWidth: 0 }))\n }\n return files\n}\n\nfunction repositoryPolicy(source: string): RepositoryPolicy {\n const value = JSON.parse(source) as unknown\n if (!plainObject(value)) throw new Error('ras-stack.policy.json must contain an object')\n for (const name of ['changesets', 'dependabot'] as const) validateSelection(name, value[name])\n const pnpm = value.pnpm\n if (pnpm !== undefined && pnpm !== false) {\n if (!plainObject(pnpm)) throw new Error('pnpm policy must be false or an object')\n const age = pnpm.minimumReleaseAge\n if (age !== undefined && (typeof age !== 'number' || !Number.isInteger(age) || age < 0)) {\n throw new Error('pnpm.minimumReleaseAge must be a non-negative integer')\n }\n }\n return value\n}\n\nfunction validateSelection(name: string, value: unknown) {\n if (value === undefined || typeof value === 'boolean') return\n if (!plainObject(value) || (value.overrides !== undefined && !plainObject(value.overrides))) {\n throw new Error(`${name} policy must be a boolean or an object with overrides`)\n }\n}\n\nfunction selectedPolicy(base: Record<string, unknown>, selection: Exclude<PolicySelection, false>) {\n return selection === true ? base : deepMerge(base, selection.overrides ?? {})\n}\n\nfunction deepMerge(base: Record<string, unknown>, overrides: Record<string, unknown>): Record<string, unknown> {\n return Object.fromEntries(\n [...new Set([...Object.keys(base), ...Object.keys(overrides)])].map((key) => {\n const left = base[key]\n const right = overrides[key]\n return [key, plainObject(left) && plainObject(right) ? deepMerge(left, right) : right === undefined ? left : right]\n }),\n )\n}\n\nfunction plainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/policy/index.ts"],"names":[],"mappings":"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","sourcesContent":["import { mkdir, readFile, writeFile } from 'node:fs/promises'\nimport { dirname, join } from 'node:path'\nimport { parseDocument, stringify } from 'yaml'\nimport { checkChangesets } from './changesets.js'\n\nconst changesetsPolicy = {\n $schema: 'https://unpkg.com/@changesets/config@3.1.2/schema.json',\n changelog: '@changesets/cli/changelog',\n commit: false,\n fixed: [],\n linked: [],\n access: 'restricted',\n privatePackages: { version: true, tag: true },\n baseBranch: 'main',\n updateInternalDependencies: 'patch',\n ignore: [],\n}\n\nconst dependabotPolicy = {\n version: 2,\n updates: [\n {\n 'package-ecosystem': 'npm',\n directory: '/',\n schedule: { interval: 'weekly' },\n cooldown: { 'default-days': 7 },\n allow: [\n {\n 'dependency-name': '*',\n 'update-types': ['version-update:semver-minor', 'version-update:semver-patch'],\n },\n ],\n groups: {\n 'patch-updates': {\n 'applies-to': 'version-updates',\n patterns: ['*'],\n 'update-types': ['patch'],\n },\n 'minor-updates': {\n 'applies-to': 'version-updates',\n patterns: ['*'],\n 'update-types': ['minor'],\n },\n },\n },\n {\n 'package-ecosystem': 'github-actions',\n directory: '/',\n schedule: { interval: 'weekly' },\n cooldown: { 'default-days': 7 },\n ignore: [\n {\n 'dependency-name': 'richardsolomou/ras-stack*',\n 'update-types': ['version-update:semver-major'],\n },\n ],\n },\n ],\n}\n\ntype PolicySelection = boolean | { overrides?: Record<string, unknown> }\n\nexport type RepositoryPolicy = {\n changesets?: PolicySelection\n dependabot?: PolicySelection\n pnpm?: false | { minimumReleaseAge?: number }\n}\n\nexport async function syncRepositoryPolicy(root: string, mode: 'check' | 'write') {\n const config = repositoryPolicy(await readFile(join(root, 'ras-stack.policy.json'), 'utf8'))\n const files = await renderedPolicyFiles(root, config)\n const compared = await Promise.all(\n [...files].map(async ([path, expected]) => {\n const absolute = join(root, path)\n const actual = await readFile(absolute, 'utf8').catch(() => undefined)\n if (actual === expected) return undefined\n if (mode === 'write') {\n await mkdir(dirname(absolute), { recursive: true })\n await writeFile(absolute, expected)\n }\n return path\n }),\n )\n return compared.filter((path) => path !== undefined)\n}\n\nexport async function checkRepositoryPolicy(root: string) {\n const config = repositoryPolicy(await readFile(join(root, 'ras-stack.policy.json'), 'utf8'))\n const files = await syncRepositoryPolicy(root, 'check')\n return [...files.map((path) => `policy drift: ${path}`), ...(config.changesets ? await checkChangesets(root) : [])]\n}\n\nexport async function renderedPolicyFiles(root: string, config: RepositoryPolicy) {\n const files = new Map<string, string>()\n if (config.changesets) {\n files.set('.changeset/config.json', `${JSON.stringify(selectedPolicy(changesetsPolicy, config.changesets), null, 2)}\\n`)\n }\n if (config.dependabot) {\n files.set('.github/dependabot.yml', stringify(selectedPolicy(dependabotPolicy, config.dependabot), { lineWidth: 0, singleQuote: true }))\n }\n if (config.pnpm) {\n const path = join(root, 'pnpm-workspace.yaml')\n const document = parseDocument(await readFile(path, 'utf8').catch(() => ''))\n document.set('minimumReleaseAge', config.pnpm.minimumReleaseAge ?? 10_080)\n files.set('pnpm-workspace.yaml', document.toString({ lineWidth: 0 }))\n }\n return files\n}\n\nfunction repositoryPolicy(source: string): RepositoryPolicy {\n const value = JSON.parse(source) as unknown\n if (!plainObject(value)) throw new Error('ras-stack.policy.json must contain an object')\n for (const name of ['changesets', 'dependabot'] as const) validateSelection(name, value[name])\n const pnpm = value.pnpm\n if (pnpm !== undefined && pnpm !== false) {\n if (!plainObject(pnpm)) throw new Error('pnpm policy must be false or an object')\n const age = pnpm.minimumReleaseAge\n if (age !== undefined && (typeof age !== 'number' || !Number.isInteger(age) || age < 0)) {\n throw new Error('pnpm.minimumReleaseAge must be a non-negative integer')\n }\n }\n return value\n}\n\nfunction validateSelection(name: string, value: unknown) {\n if (value === undefined || typeof value === 'boolean') return\n if (!plainObject(value) || (value.overrides !== undefined && !plainObject(value.overrides))) {\n throw new Error(`${name} policy must be a boolean or an object with overrides`)\n }\n}\n\nfunction selectedPolicy(base: Record<string, unknown>, selection: Exclude<PolicySelection, false>) {\n return selection === true ? base : deepMerge(base, selection.overrides ?? {})\n}\n\nfunction deepMerge(base: Record<string, unknown>, overrides: Record<string, unknown>): Record<string, unknown> {\n return Object.fromEntries(\n [...new Set([...Object.keys(base), ...Object.keys(overrides)])].map((key) => {\n const left = base[key]\n const right = overrides[key]\n return [key, plainObject(left) && plainObject(right) ? deepMerge(left, right) : right === undefined ? left : right]\n }),\n )\n}\n\nfunction plainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n"]}
@@ -4,7 +4,7 @@ type ViteProxyTarget = {
4
4
  changeOrigin: true;
5
5
  rewrite: (path: string) => string;
6
6
  };
7
- export declare const POSTHOG_DEFAULT_INGEST_PATH = "/ingest";
7
+ export declare const POSTHOG_DEFAULT_INGEST_PATH = "/t";
8
8
  export declare function postHogIngestProxy(input: Pick<PostHogEnvironment, 'host' | 'assetsHost'>, options?: {
9
9
  path?: string;
10
10
  }): {
@@ -1,13 +1,17 @@
1
1
  import { postHogHttpUrl } from './config.js';
2
2
  function viteTarget(host, ingestPath) {
3
- const prefix = new RegExp(`^${ingestPath.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}`);
3
+ const prefix = new RegExp(`^${escapeRegExp(ingestPath)}`);
4
4
  return {
5
5
  target: host,
6
6
  changeOrigin: true,
7
7
  rewrite: (path) => path.replace(prefix, ''),
8
8
  };
9
9
  }
10
- export const POSTHOG_DEFAULT_INGEST_PATH = '/ingest';
10
+ function escapeRegExp(value) {
11
+ return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
12
+ }
13
+ // Ad-blocker lists match the literal `/ingest` segment on any host, so the default avoids it.
14
+ export const POSTHOG_DEFAULT_INGEST_PATH = '/t';
11
15
  function normalizedIngestPath(path) {
12
16
  if (!path.startsWith('/') || path.endsWith('/') || path.includes('?') || path.includes('#')) {
13
17
  throw new Error('PostHog ingest path must start with "/", have no trailing slash, and carry no query or fragment');
@@ -18,12 +22,14 @@ export function postHogIngestProxy(input, options = {}) {
18
22
  const ingestionHost = postHogHttpUrl(input.host, 'host');
19
23
  const assetsHost = postHogHttpUrl(input.assetsHost, 'assetsHost');
20
24
  const path = normalizedIngestPath(options.path ?? POSTHOG_DEFAULT_INGEST_PATH);
25
+ // Vite matches string keys as bare prefixes, so a short path needs a regular expression bound to its segment.
26
+ const segment = `^${escapeRegExp(path)}`;
21
27
  return {
22
28
  path,
23
29
  vite: {
24
- [`${path}/static`]: viteTarget(assetsHost, path),
25
- [`${path}/array`]: viteTarget(assetsHost, path),
26
- [path]: viteTarget(ingestionHost, path),
30
+ [`${segment}/static`]: viteTarget(assetsHost, path),
31
+ [`${segment}/array`]: viteTarget(assetsHost, path),
32
+ [`${segment}(?:/|$)`]: viteTarget(ingestionHost, path),
27
33
  },
28
34
  nitro: {
29
35
  [`${path}/static/**`]: { proxy: `${assetsHost}/static/**` },
@@ -1 +1 @@
1
- {"version":3,"file":"proxy.js","sourceRoot":"","sources":["../../src/posthog/proxy.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAS5C,SAAS,UAAU,CAAC,IAAY,EAAE,UAAkB;IAClD,MAAM,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,UAAU,CAAC,OAAO,CAAC,qBAAqB,EAAE,MAAM,CAAC,EAAE,CAAC,CAAA;IAClF,OAAO;QACL,MAAM,EAAE,IAAI;QACZ,YAAY,EAAE,IAAI;QAClB,OAAO,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC;KAC5C,CAAA;AACH,CAAC;AAED,MAAM,CAAC,MAAM,2BAA2B,GAAG,SAAS,CAAA;AAEpD,SAAS,oBAAoB,CAAC,IAAY;IACxC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QAC5F,MAAM,IAAI,KAAK,CAAC,iGAAiG,CAAC,CAAA;IACpH,CAAC;IACD,OAAO,IAAI,CAAA;AACb,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,KAAsD,EAAE,OAAO,GAAsB,EAAE;IACxH,MAAM,aAAa,GAAG,cAAc,CAAC,KAAK,CAAC,IAAI,EAAE,MAAM,CAAC,CAAA;IACxD,MAAM,UAAU,GAAG,cAAc,CAAC,KAAK,CAAC,UAAU,EAAE,YAAY,CAAC,CAAA;IACjE,MAAM,IAAI,GAAG,oBAAoB,CAAC,OAAO,CAAC,IAAI,IAAI,2BAA2B,CAAC,CAAA;IAC9E,OAAO;QACL,IAAI;QACJ,IAAI,EAAE;YACJ,CAAC,GAAG,IAAI,SAAS,CAAC,EAAE,UAAU,CAAC,UAAU,EAAE,IAAI,CAAC;YAChD,CAAC,GAAG,IAAI,QAAQ,CAAC,EAAE,UAAU,CAAC,UAAU,EAAE,IAAI,CAAC;YAC/C,CAAC,IAAI,CAAC,EAAE,UAAU,CAAC,aAAa,EAAE,IAAI,CAAC;SACxC;QACD,KAAK,EAAE;YACL,CAAC,GAAG,IAAI,YAAY,CAAC,EAAE,EAAE,KAAK,EAAE,GAAG,UAAU,YAAY,EAAE;YAC3D,CAAC,GAAG,IAAI,WAAW,CAAC,EAAE,EAAE,KAAK,EAAE,GAAG,UAAU,WAAW,EAAE;YACzD,CAAC,GAAG,IAAI,KAAK,CAAC,EAAE,EAAE,KAAK,EAAE,GAAG,aAAa,KAAK,EAAE;SACjD;KACF,CAAA;AACH,CAAC","sourcesContent":["import { postHogHttpUrl } from './config.js'\nimport type { PostHogEnvironment } from './config.js'\n\ntype ViteProxyTarget = {\n target: string\n changeOrigin: true\n rewrite: (path: string) => string\n}\n\nfunction viteTarget(host: string, ingestPath: string): ViteProxyTarget {\n const prefix = new RegExp(`^${ingestPath.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')}`)\n return {\n target: host,\n changeOrigin: true,\n rewrite: (path) => path.replace(prefix, ''),\n }\n}\n\nexport const POSTHOG_DEFAULT_INGEST_PATH = '/ingest'\n\nfunction normalizedIngestPath(path: string): string {\n if (!path.startsWith('/') || path.endsWith('/') || path.includes('?') || path.includes('#')) {\n throw new Error('PostHog ingest path must start with \"/\", have no trailing slash, and carry no query or fragment')\n }\n return path\n}\n\nexport function postHogIngestProxy(input: Pick<PostHogEnvironment, 'host' | 'assetsHost'>, options: { path?: string } = {}) {\n const ingestionHost = postHogHttpUrl(input.host, 'host')\n const assetsHost = postHogHttpUrl(input.assetsHost, 'assetsHost')\n const path = normalizedIngestPath(options.path ?? POSTHOG_DEFAULT_INGEST_PATH)\n return {\n path,\n vite: {\n [`${path}/static`]: viteTarget(assetsHost, path),\n [`${path}/array`]: viteTarget(assetsHost, path),\n [path]: viteTarget(ingestionHost, path),\n },\n nitro: {\n [`${path}/static/**`]: { proxy: `${assetsHost}/static/**` },\n [`${path}/array/**`]: { proxy: `${assetsHost}/array/**` },\n [`${path}/**`]: { proxy: `${ingestionHost}/**` },\n },\n }\n}\n"]}
1
+ {"version":3,"file":"proxy.js","sourceRoot":"","sources":["../../src/posthog/proxy.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAS5C,SAAS,UAAU,CAAC,IAAY,EAAE,UAAkB;IAClD,MAAM,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,YAAY,CAAC,UAAU,CAAC,EAAE,CAAC,CAAA;IACzD,OAAO;QACL,MAAM,EAAE,IAAI;QACZ,YAAY,EAAE,IAAI;QAClB,OAAO,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC;KAC5C,CAAA;AACH,CAAC;AAED,SAAS,YAAY,CAAC,KAAa;IACjC,OAAO,KAAK,CAAC,OAAO,CAAC,qBAAqB,EAAE,MAAM,CAAC,CAAA;AACrD,CAAC;AAED,8FAA8F;AAC9F,MAAM,CAAC,MAAM,2BAA2B,GAAG,IAAI,CAAA;AAE/C,SAAS,oBAAoB,CAAC,IAAY;IACxC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QAC5F,MAAM,IAAI,KAAK,CAAC,iGAAiG,CAAC,CAAA;IACpH,CAAC;IACD,OAAO,IAAI,CAAA;AACb,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,KAAsD,EAAE,OAAO,GAAsB,EAAE;IACxH,MAAM,aAAa,GAAG,cAAc,CAAC,KAAK,CAAC,IAAI,EAAE,MAAM,CAAC,CAAA;IACxD,MAAM,UAAU,GAAG,cAAc,CAAC,KAAK,CAAC,UAAU,EAAE,YAAY,CAAC,CAAA;IACjE,MAAM,IAAI,GAAG,oBAAoB,CAAC,OAAO,CAAC,IAAI,IAAI,2BAA2B,CAAC,CAAA;IAC9E,8GAA8G;IAC9G,MAAM,OAAO,GAAG,IAAI,YAAY,CAAC,IAAI,CAAC,EAAE,CAAA;IACxC,OAAO;QACL,IAAI;QACJ,IAAI,EAAE;YACJ,CAAC,GAAG,OAAO,SAAS,CAAC,EAAE,UAAU,CAAC,UAAU,EAAE,IAAI,CAAC;YACnD,CAAC,GAAG,OAAO,QAAQ,CAAC,EAAE,UAAU,CAAC,UAAU,EAAE,IAAI,CAAC;YAClD,CAAC,GAAG,OAAO,SAAS,CAAC,EAAE,UAAU,CAAC,aAAa,EAAE,IAAI,CAAC;SACvD;QACD,KAAK,EAAE;YACL,CAAC,GAAG,IAAI,YAAY,CAAC,EAAE,EAAE,KAAK,EAAE,GAAG,UAAU,YAAY,EAAE;YAC3D,CAAC,GAAG,IAAI,WAAW,CAAC,EAAE,EAAE,KAAK,EAAE,GAAG,UAAU,WAAW,EAAE;YACzD,CAAC,GAAG,IAAI,KAAK,CAAC,EAAE,EAAE,KAAK,EAAE,GAAG,aAAa,KAAK,EAAE;SACjD;KACF,CAAA;AACH,CAAC","sourcesContent":["import { postHogHttpUrl } from './config.js'\nimport type { PostHogEnvironment } from './config.js'\n\ntype ViteProxyTarget = {\n target: string\n changeOrigin: true\n rewrite: (path: string) => string\n}\n\nfunction viteTarget(host: string, ingestPath: string): ViteProxyTarget {\n const prefix = new RegExp(`^${escapeRegExp(ingestPath)}`)\n return {\n target: host,\n changeOrigin: true,\n rewrite: (path) => path.replace(prefix, ''),\n }\n}\n\nfunction escapeRegExp(value: string) {\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n}\n\n// Ad-blocker lists match the literal `/ingest` segment on any host, so the default avoids it.\nexport const POSTHOG_DEFAULT_INGEST_PATH = '/t'\n\nfunction normalizedIngestPath(path: string): string {\n if (!path.startsWith('/') || path.endsWith('/') || path.includes('?') || path.includes('#')) {\n throw new Error('PostHog ingest path must start with \"/\", have no trailing slash, and carry no query or fragment')\n }\n return path\n}\n\nexport function postHogIngestProxy(input: Pick<PostHogEnvironment, 'host' | 'assetsHost'>, options: { path?: string } = {}) {\n const ingestionHost = postHogHttpUrl(input.host, 'host')\n const assetsHost = postHogHttpUrl(input.assetsHost, 'assetsHost')\n const path = normalizedIngestPath(options.path ?? POSTHOG_DEFAULT_INGEST_PATH)\n // Vite matches string keys as bare prefixes, so a short path needs a regular expression bound to its segment.\n const segment = `^${escapeRegExp(path)}`\n return {\n path,\n vite: {\n [`${segment}/static`]: viteTarget(assetsHost, path),\n [`${segment}/array`]: viteTarget(assetsHost, path),\n [`${segment}(?:/|$)`]: viteTarget(ingestionHost, path),\n },\n nitro: {\n [`${path}/static/**`]: { proxy: `${assetsHost}/static/**` },\n [`${path}/array/**`]: { proxy: `${assetsHost}/array/**` },\n [`${path}/**`]: { proxy: `${ingestionHost}/**` },\n },\n }\n}\n"]}
@@ -1,5 +1,5 @@
1
1
  import type { CaptureMetricOptions, PostHog, PostHogOptions, Span, StartSpanOptions } from 'posthog-node';
2
- import type { RpcErrorContext, RpcLogger } from '../server/rpc.js';
2
+ import type { RpcErrorContext, RpcLogger, RpcObserver } from '../server/rpc.js';
3
3
  import type { PostHogEnvironment } from './config.js';
4
4
  import { type PostHogRequestContextOptions } from './request.js';
5
5
  export type PostHogLogValue = string | number | boolean | null | PostHogLogValue[];
@@ -31,6 +31,10 @@ export type PostHogRpcLoggerOptions = {
31
31
  allowAnonymousDistinctId?: boolean;
32
32
  fallbackDistinctId?: string;
33
33
  };
34
+ export type PostHogRpcObserverOptions = {
35
+ metricPrefix?: string;
36
+ spanName?: string;
37
+ };
34
38
  type PostHogShutdownProcess = {
35
39
  pid: number;
36
40
  on(signal: 'SIGINT' | 'SIGTERM', listener: () => void): void;
@@ -60,5 +64,6 @@ export declare function createManagedPostHogServerTelemetry(options: ManagedPost
60
64
  shutdown(timeoutMs?: number): Promise<void>;
61
65
  };
62
66
  export declare function createPostHogRpcLogger(telemetry: PostHogServerTelemetry, options?: PostHogRpcLoggerOptions): RpcLogger;
67
+ export declare function createPostHogRpcObserver(telemetry: Pick<PostHogServerTelemetry, 'metrics' | 'withRequestContext' | 'withSpan'>, options?: PostHogRpcObserverOptions): RpcObserver;
63
68
  export declare function installPostHogServerTelemetryShutdown(telemetry: Pick<PostHogServerTelemetry, 'shutdown'>, target?: PostHogShutdownProcess): () => void;
64
69
  export {};
@@ -230,6 +230,37 @@ export function createPostHogRpcLogger(telemetry, options = {}) {
230
230
  ]);
231
231
  };
232
232
  }
233
+ export function createPostHogRpcObserver(telemetry, options = {}) {
234
+ const metricPrefix = options.metricPrefix ?? 'server_function';
235
+ const spanName = options.spanName ?? 'server_function';
236
+ return async function observe(request, work) {
237
+ if (!request)
238
+ return work();
239
+ const method = request.method.toUpperCase();
240
+ const attributes = { method };
241
+ const parent = request.headers.get('traceparent');
242
+ return telemetry.withRequestContext(request, {}, () => telemetry.withSpan(spanName, { kind: 'server', ...(parent ? { parent } : {}) }, async (span) => {
243
+ const startedAt = performance.now();
244
+ span?.setAttribute('http.request.method', method);
245
+ let outcome = 'error';
246
+ try {
247
+ const result = await work();
248
+ outcome = 'success';
249
+ return result;
250
+ }
251
+ finally {
252
+ span?.setAttribute('server.function.outcome', outcome);
253
+ await Promise.all([
254
+ telemetry.metrics.count(`${metricPrefix}.requests`, 1, { attributes: { ...attributes, outcome } }),
255
+ telemetry.metrics.histogram(`${metricPrefix}.duration`, Math.max(0, performance.now() - startedAt), {
256
+ unit: 'ms',
257
+ attributes,
258
+ }),
259
+ ]);
260
+ }
261
+ }));
262
+ };
263
+ }
233
264
  export function installPostHogServerTelemetryShutdown(telemetry, target = process) {
234
265
  let shuttingDown = false;
235
266
  const listeners = new Map();