intlayer-editor 9.6.0 → 9.7.1

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 (99) hide show
  1. package/bin/intlayer-editor.mjs +29 -7
  2. package/client/dist/assets/CodeBlockShiki-BaQlmvsF.js +2 -0
  3. package/client/dist/assets/MarkDownRender-CufSxUcK.js +25 -0
  4. package/client/dist/assets/MarkDownRender-HV9SKc56.js +1 -0
  5. package/client/dist/assets/MarkdownEditor-BSbSD-tQ.js +174 -0
  6. package/client/dist/assets/{astro-JHDYjn39.js → astro-BZ97kZFP.js} +1 -1
  7. package/client/dist/assets/codeTransformer-pmm6yJGh.js +9 -0
  8. package/client/dist/assets/compat-BqYysBkt.js +1 -0
  9. package/client/dist/assets/css-BGIxQoYs.js +1 -0
  10. package/client/dist/assets/css-DgaLhXQH.js +1 -0
  11. package/client/dist/assets/dist-VBWB9uC8.js +11 -0
  12. package/client/dist/assets/extractUrlFromSitemap-Chj1JVpo.js +1 -0
  13. package/client/dist/assets/html-BCy9P6D2.js +1 -0
  14. package/client/dist/assets/{html-B2cKP6UN.js → html-DSw1lh5G.js} +1 -1
  15. package/client/dist/assets/index-65aMFXUF.css +2 -0
  16. package/client/dist/assets/index-MyctEsPU.js +116 -0
  17. package/client/dist/assets/javascript-CpIGwZ_Y.js +1 -0
  18. package/client/dist/assets/json-C3WlssX3.js +1 -0
  19. package/client/dist/assets/purify.es-Dqk5qvad.js +3 -0
  20. package/client/dist/assets/shikiBundle-DbyGgq42.js +12 -0
  21. package/client/dist/assets/{svelte-tEuLYt3E.js → svelte-CdeT-Ich.js} +1 -1
  22. package/client/dist/assets/tsx-CGosfAUg.js +1 -0
  23. package/client/dist/assets/typescript-Bb1J8qyy.js +1 -0
  24. package/client/dist/assets/{vue-Bg0byEaI.js → vue-jUI-lpPb.js} +1 -1
  25. package/client/dist/assets/wasm-inlined-4KdTHw0F.js +1 -0
  26. package/client/dist/index.html +3 -6
  27. package/package.json +37 -37
  28. package/server/dist/_virtual/_rolldown/runtime.cjs +1 -2
  29. package/server/dist/controllers/auth.controller.cjs +79 -0
  30. package/server/dist/controllers/auth.controller.cjs.map +1 -0
  31. package/server/dist/controllers/auth.controller.d.ts +34 -0
  32. package/server/dist/controllers/auth.controller.mjs +75 -0
  33. package/server/dist/controllers/auth.controller.mjs.map +1 -0
  34. package/server/dist/controllers/configuration.controller.cjs +10 -5
  35. package/server/dist/controllers/configuration.controller.cjs.map +1 -1
  36. package/server/dist/controllers/configuration.controller.d.ts +8 -3
  37. package/server/dist/controllers/configuration.controller.mjs +10 -5
  38. package/server/dist/controllers/configuration.controller.mjs.map +1 -1
  39. package/server/dist/controllers/dictionary.controller.cjs +4 -4
  40. package/server/dist/controllers/dictionary.controller.cjs.map +1 -1
  41. package/server/dist/controllers/dictionary.controller.d.ts +1 -2
  42. package/server/dist/controllers/dictionary.controller.mjs +3 -3
  43. package/server/dist/controllers/dictionary.controller.mjs.map +1 -1
  44. package/server/dist/export.cjs +2 -2
  45. package/server/dist/export.d.ts +2 -2
  46. package/server/dist/index.cjs +56 -19
  47. package/server/dist/index.cjs.map +1 -1
  48. package/server/dist/index.mjs +56 -19
  49. package/server/dist/index.mjs.map +1 -1
  50. package/server/dist/routes/auth.routes.cjs +34 -0
  51. package/server/dist/routes/auth.routes.cjs.map +1 -0
  52. package/server/dist/routes/auth.routes.d.ts +20 -0
  53. package/server/dist/routes/auth.routes.mjs +32 -0
  54. package/server/dist/routes/auth.routes.mjs.map +1 -0
  55. package/server/dist/routes/config.routes.cjs +2 -3
  56. package/server/dist/routes/config.routes.d.ts +0 -2
  57. package/server/dist/routes/config.routes.mjs +1 -2
  58. package/server/dist/routes/dictionary.routes.cjs +2 -3
  59. package/server/dist/routes/dictionary.routes.d.ts +0 -2
  60. package/server/dist/routes/dictionary.routes.mjs +1 -2
  61. package/server/dist/services/editorAuth.service.cjs +142 -0
  62. package/server/dist/services/editorAuth.service.cjs.map +1 -0
  63. package/server/dist/services/editorAuth.service.d.ts +41 -0
  64. package/server/dist/services/editorAuth.service.mjs +136 -0
  65. package/server/dist/services/editorAuth.service.mjs.map +1 -0
  66. package/server/dist/types/Routes.d.ts +0 -2
  67. package/server/dist/utils/checkPortAvailability.cjs +40 -15
  68. package/server/dist/utils/checkPortAvailability.cjs.map +1 -1
  69. package/server/dist/utils/checkPortAvailability.d.ts +14 -3
  70. package/server/dist/utils/checkPortAvailability.mjs +39 -15
  71. package/server/dist/utils/checkPortAvailability.mjs.map +1 -1
  72. package/server/dist/utils/httpStatusCodes.cjs +1 -2
  73. package/server/dist/utils/httpStatusCodes.d.ts +9 -20
  74. package/server/dist/utils/httpStatusCodes.mjs +1 -2
  75. package/server/dist/utils/responseData.cjs +2 -3
  76. package/server/dist/utils/responseData.d.ts +1 -2
  77. package/server/dist/utils/responseData.mjs +1 -2
  78. package/client/dist/assets/CodeBlockShiki-DtgHBhf-.js +0 -2
  79. package/client/dist/assets/MarkDownRender-B5rc5xdv.js +0 -25
  80. package/client/dist/assets/MarkDownRender-BJfBvDXr.js +0 -1
  81. package/client/dist/assets/MarkdownEditor-CKaCMWJN.js +0 -160
  82. package/client/dist/assets/codeTransformer-DVPQQcdP.js +0 -9
  83. package/client/dist/assets/css-CdtkT07m.js +0 -1
  84. package/client/dist/assets/css-LbU1hoFO.js +0 -1
  85. package/client/dist/assets/dist-BHukwvnD.js +0 -11
  86. package/client/dist/assets/dist-CcmWBeAo.js +0 -1
  87. package/client/dist/assets/dynamic-BG2syiTL.js +0 -41
  88. package/client/dist/assets/html-DYtkB_T1.js +0 -1
  89. package/client/dist/assets/index-BtBJMAbp.js +0 -59
  90. package/client/dist/assets/index-D82ZIHVs.css +0 -2
  91. package/client/dist/assets/javascript-Yelw-5ZN.js +0 -1
  92. package/client/dist/assets/json-CaljABEy.js +0 -1
  93. package/client/dist/assets/purify.es-Bvo9QlJ8.js +0 -3
  94. package/client/dist/assets/react-Cvdyeg_0.js +0 -1
  95. package/client/dist/assets/rolldown-runtime-hePW80VL.js +0 -1
  96. package/client/dist/assets/shikiBundle-vOk0aVqf.js +0 -12
  97. package/client/dist/assets/tsx-5f3ulCja.js +0 -1
  98. package/client/dist/assets/typescript-Utq2Cl8c.js +0 -1
  99. package/client/dist/assets/wasm-BnjxR4X6.js +0 -1
@@ -0,0 +1,142 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
+ const require_runtime = require("../_virtual/_rolldown/runtime.cjs");
3
+ let _intlayer_config_colors = require("@intlayer/config/colors");
4
+ _intlayer_config_colors = require_runtime.__toESM(_intlayer_config_colors, 1);
5
+ let _intlayer_config_logger = require("@intlayer/config/logger");
6
+ let _intlayer_api = require("@intlayer/api");
7
+ let _intlayer_cli = require("@intlayer/cli");
8
+
9
+ // src/services/editorAuth.service.ts
10
+ /** Time after which an unanswered login is given up. */
11
+ const LOGIN_TIMEOUT_MS = 3e5;
12
+ /** Margin before expiry under which a cached access token is renewed. */
13
+ const TOKEN_RENEWAL_MARGIN_MS = 6e4;
14
+ /** Credentials picked during an editor-triggered login, kept for this run. */
15
+ let runtimeCredentials = null;
16
+ /** Access token exchanged from the access key, reused until it expires. */
17
+ let accessKeyAuthCache = null;
18
+ /** Identity bound to each CLI session token, resolved once per token. */
19
+ const sessionIdentityCache = /* @__PURE__ */ new Map();
20
+ /** Aborts the login flow waiting for the browser callback, if any. */
21
+ let pendingLoginController = null;
22
+ const isExpiringSoon = (expiresAt) => expiresAt !== null && new Date(expiresAt).getTime() - TOKEN_RENEWAL_MARGIN_MS <= Date.now();
23
+ const getCredentials = (configuration) => {
24
+ if (runtimeCredentials) return runtimeCredentials;
25
+ const { clientId, clientSecret } = configuration.editor;
26
+ return clientId && clientSecret ? {
27
+ clientId,
28
+ clientSecret
29
+ } : null;
30
+ };
31
+ /**
32
+ * Resolves the `intlayer login` session token, shared with the CLI through
33
+ * the temp directory. `null` when absent, expired, or rejected by the CMS.
34
+ */
35
+ const resolveSessionAuth = async (configuration) => {
36
+ const sessionData = await (0, _intlayer_cli.readCliSessionToken)(configuration);
37
+ if (!sessionData) return null;
38
+ if (!sessionIdentityCache.has(sessionData.token)) try {
39
+ const result = await (0, _intlayer_api.getIntlayerAPIProxy)(void 0, configuration, sessionData.token).oAuth.getCliSessionMe();
40
+ sessionIdentityCache.set(sessionData.token, {
41
+ user: result.data?.user ?? null,
42
+ organization: result.data?.organization ?? null,
43
+ project: result.data?.project ?? null
44
+ });
45
+ } catch {
46
+ return null;
47
+ }
48
+ return {
49
+ accessToken: sessionData.token,
50
+ expiresAt: sessionData.expiresAt,
51
+ authType: "session",
52
+ ...sessionIdentityCache.get(sessionData.token)
53
+ };
54
+ };
55
+ /**
56
+ * Exchanges the access key for an access token. The client secret stays on
57
+ * the server: only the resulting short-lived token reaches the browser.
58
+ */
59
+ const resolveAccessKeyAuth = async (configuration) => {
60
+ const credentials = getCredentials(configuration);
61
+ if (!credentials) return null;
62
+ if (accessKeyAuthCache?.clientId === credentials.clientId && !isExpiringSoon(accessKeyAuthCache.expiresAt)) {
63
+ const { clientId: _clientId, ...cachedAuth } = accessKeyAuthCache;
64
+ return cachedAuth;
65
+ }
66
+ try {
67
+ const token = (await (0, _intlayer_api.getOAuthAPI)(void 0, { editor: {
68
+ ...configuration.editor,
69
+ ...credentials
70
+ } }).getOAuth2AccessToken()).data;
71
+ if (!token?.accessToken) return null;
72
+ const accessKeyAuth = {
73
+ accessToken: token.accessToken,
74
+ expiresAt: token.accessTokenExpiresAt ? new Date(token.accessTokenExpiresAt).toISOString() : null,
75
+ authType: "accessKey",
76
+ user: token.user,
77
+ organization: token.organization,
78
+ project: token.project
79
+ };
80
+ accessKeyAuthCache = {
81
+ ...accessKeyAuth,
82
+ clientId: credentials.clientId
83
+ };
84
+ return accessKeyAuth;
85
+ } catch {
86
+ accessKeyAuthCache = null;
87
+ return null;
88
+ }
89
+ };
90
+ /**
91
+ * Resolves the CMS identity of the editor, in the same order as the CLI:
92
+ * `intlayer login` session first, then the access key.
93
+ */
94
+ const resolveEditorAuth = async (configuration) => await resolveSessionAuth(configuration) ?? await resolveAccessKeyAuth(configuration);
95
+ /**
96
+ * Signs the editor out: drops the `intlayer login` session (shared with the
97
+ * CLI), the pending login and the access key loaded during this run. An access
98
+ * key set in the configuration stays in use, as for the CLI.
99
+ */
100
+ const logoutEditor = async (configuration) => {
101
+ pendingLoginController?.abort();
102
+ runtimeCredentials = null;
103
+ accessKeyAuthCache = null;
104
+ sessionIdentityCache.clear();
105
+ await (0, _intlayer_cli.clearCliSessionToken)(configuration);
106
+ };
107
+ /** Whether an editor-triggered login is waiting for the browser callback. */
108
+ const isEditorLoginPending = () => pendingLoginController !== null;
109
+ /**
110
+ * Starts the `intlayer login` browser flow. A login still pending is dropped
111
+ * first, so a closed login tab can be reopened. Resolves once the CMS has
112
+ * called back (the session token being stored for both the editor and the
113
+ * CLI), or when the login is dropped or times out.
114
+ */
115
+ const startEditorLogin = async (configuration, configOptions) => {
116
+ pendingLoginController?.abort();
117
+ const appLogger = (0, _intlayer_config_logger.getAppLogger)(configuration);
118
+ const loginController = new AbortController();
119
+ const timeout = setTimeout(() => loginController.abort(), LOGIN_TIMEOUT_MS);
120
+ pendingLoginController = loginController;
121
+ await (0, _intlayer_cli.login)({
122
+ exitAfter: false,
123
+ configOptions,
124
+ signal: loginController.signal,
125
+ onCredentials: (credentials) => {
126
+ runtimeCredentials = credentials;
127
+ accessKeyAuthCache = null;
128
+ appLogger("Access key loaded for this editor session. Insert it in your .env file to keep it:");
129
+ appLogger(`${(0, _intlayer_config_logger.colorize)("INTLAYER_CLIENT_ID=", _intlayer_config_colors.GREY_LIGHT)}${(0, _intlayer_config_logger.colorize)(credentials.clientId, _intlayer_config_colors.BLUE)}`);
130
+ appLogger(`${(0, _intlayer_config_logger.colorize)("INTLAYER_CLIENT_SECRET=", _intlayer_config_colors.GREY_LIGHT)}${(0, _intlayer_config_logger.colorize)(credentials.clientSecret, _intlayer_config_colors.BLUE)}`);
131
+ }
132
+ }).finally(() => {
133
+ clearTimeout(timeout);
134
+ if (pendingLoginController === loginController) pendingLoginController = null;
135
+ });
136
+ };
137
+
138
+ exports.isEditorLoginPending = isEditorLoginPending;
139
+ exports.logoutEditor = logoutEditor;
140
+ exports.resolveEditorAuth = resolveEditorAuth;
141
+ exports.startEditorLogin = startEditorLogin;
142
+ //# sourceMappingURL=editorAuth.service.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"editorAuth.service.cjs","names":["readCliSessionToken","getIntlayerAPIProxy","getOAuthAPI","clearCliSessionToken","getAppLogger","login","colorize","ANSIColors"],"sources":["../../src/services/editorAuth.service.ts"],"sourcesContent":["import { getIntlayerAPIProxy, getOAuthAPI } from '@intlayer/api';\nimport type { OrganizationAPI } from '@intlayer/backend-contract/organization';\nimport type { ProjectAPI } from '@intlayer/backend-contract/project';\nimport type { UserAPI } from '@intlayer/backend-contract/user';\nimport {\n clearCliSessionToken,\n login,\n readCliSessionToken,\n} from '@intlayer/cli';\nimport * as ANSIColors from '@intlayer/config/colors';\nimport { colorize, getAppLogger } from '@intlayer/config/logger';\nimport type { GetConfigurationOptions } from '@intlayer/config/node';\nimport type { IntlayerConfig } from '@intlayer/types/config';\n\n/** Access-key credentials used for the client-credentials exchange. */\ntype EditorCredentials = {\n clientId: string;\n clientSecret: string;\n};\n\n/**\n * Identity the editor client uses to call the CMS, sent as a bearer token.\n *\n * - `session`: the user's short-lived `intlayer login` session token\n * - `accessKey`: an access token exchanged from the project access key\n */\nexport type EditorAuth = {\n accessToken: string;\n /** ISO date, `null` when the token never expires. */\n expiresAt: string | null;\n authType: 'session' | 'accessKey';\n user?: UserAPI | null;\n organization?: OrganizationAPI | null;\n project?: ProjectAPI | null;\n};\n\n/** Time after which an unanswered login is given up. */\nconst LOGIN_TIMEOUT_MS = 5 * 60 * 1000;\n\n/** Margin before expiry under which a cached access token is renewed. */\nconst TOKEN_RENEWAL_MARGIN_MS = 60 * 1000;\n\n/** Credentials picked during an editor-triggered login, kept for this run. */\nlet runtimeCredentials: EditorCredentials | null = null;\n\n/** Access token exchanged from the access key, reused until it expires. */\nlet accessKeyAuthCache: (EditorAuth & { clientId: string }) | null = null;\n\n/** Identity bound to a CLI session token. */\ntype SessionIdentity = Pick<EditorAuth, 'user' | 'organization' | 'project'>;\n\n/** Identity bound to each CLI session token, resolved once per token. */\nconst sessionIdentityCache = new Map<string, SessionIdentity>();\n\n/** Aborts the login flow waiting for the browser callback, if any. */\nlet pendingLoginController: AbortController | null = null;\n\nconst isExpiringSoon = (expiresAt: string | null): boolean =>\n expiresAt !== null &&\n new Date(expiresAt).getTime() - TOKEN_RENEWAL_MARGIN_MS <= Date.now();\n\nconst getCredentials = (\n configuration: IntlayerConfig\n): EditorCredentials | null => {\n if (runtimeCredentials) return runtimeCredentials;\n\n const { clientId, clientSecret } = configuration.editor;\n\n return clientId && clientSecret ? { clientId, clientSecret } : null;\n};\n\n/**\n * Resolves the `intlayer login` session token, shared with the CLI through\n * the temp directory. `null` when absent, expired, or rejected by the CMS.\n */\nconst resolveSessionAuth = async (\n configuration: IntlayerConfig\n): Promise<EditorAuth | null> => {\n const sessionData = await readCliSessionToken(configuration);\n\n if (!sessionData) return null;\n\n if (!sessionIdentityCache.has(sessionData.token)) {\n try {\n const result = await getIntlayerAPIProxy(\n undefined,\n configuration,\n sessionData.token\n ).oAuth.getCliSessionMe();\n\n sessionIdentityCache.set(sessionData.token, {\n user: result.data?.user ?? null,\n organization: result.data?.organization ?? null,\n project: result.data?.project ?? null,\n });\n } catch {\n // Revoked or unreachable: fall back to the access key, if any\n return null;\n }\n }\n\n return {\n accessToken: sessionData.token,\n expiresAt: sessionData.expiresAt,\n authType: 'session',\n ...sessionIdentityCache.get(sessionData.token),\n };\n};\n\n/**\n * Exchanges the access key for an access token. The client secret stays on\n * the server: only the resulting short-lived token reaches the browser.\n */\nconst resolveAccessKeyAuth = async (\n configuration: IntlayerConfig\n): Promise<EditorAuth | null> => {\n const credentials = getCredentials(configuration);\n\n if (!credentials) return null;\n\n if (\n accessKeyAuthCache?.clientId === credentials.clientId &&\n !isExpiringSoon(accessKeyAuthCache.expiresAt)\n ) {\n const { clientId: _clientId, ...cachedAuth } = accessKeyAuthCache;\n return cachedAuth;\n }\n\n try {\n const result = await getOAuthAPI(undefined, {\n editor: { ...configuration.editor, ...credentials },\n }).getOAuth2AccessToken();\n\n const token = result.data;\n\n if (!token?.accessToken) return null;\n\n const accessKeyAuth: EditorAuth = {\n accessToken: token.accessToken,\n expiresAt: token.accessTokenExpiresAt\n ? new Date(token.accessTokenExpiresAt).toISOString()\n : null,\n authType: 'accessKey',\n user: token.user,\n organization: token.organization,\n project: token.project,\n };\n\n accessKeyAuthCache = { ...accessKeyAuth, clientId: credentials.clientId };\n\n return accessKeyAuth;\n } catch {\n accessKeyAuthCache = null;\n return null;\n }\n};\n\n/**\n * Resolves the CMS identity of the editor, in the same order as the CLI:\n * `intlayer login` session first, then the access key.\n */\nexport const resolveEditorAuth = async (\n configuration: IntlayerConfig\n): Promise<EditorAuth | null> =>\n (await resolveSessionAuth(configuration)) ??\n (await resolveAccessKeyAuth(configuration));\n\n/**\n * Signs the editor out: drops the `intlayer login` session (shared with the\n * CLI), the pending login and the access key loaded during this run. An access\n * key set in the configuration stays in use, as for the CLI.\n */\nexport const logoutEditor = async (\n configuration: IntlayerConfig\n): Promise<void> => {\n pendingLoginController?.abort();\n runtimeCredentials = null;\n accessKeyAuthCache = null;\n sessionIdentityCache.clear();\n\n await clearCliSessionToken(configuration);\n};\n\n/** Whether an editor-triggered login is waiting for the browser callback. */\nexport const isEditorLoginPending = (): boolean =>\n pendingLoginController !== null;\n\n/**\n * Starts the `intlayer login` browser flow. A login still pending is dropped\n * first, so a closed login tab can be reopened. Resolves once the CMS has\n * called back (the session token being stored for both the editor and the\n * CLI), or when the login is dropped or times out.\n */\nexport const startEditorLogin = async (\n configuration: IntlayerConfig,\n configOptions?: GetConfigurationOptions\n): Promise<void> => {\n pendingLoginController?.abort();\n\n const appLogger = getAppLogger(configuration);\n const loginController = new AbortController();\n const timeout = setTimeout(() => loginController.abort(), LOGIN_TIMEOUT_MS);\n\n pendingLoginController = loginController;\n\n await login({\n exitAfter: false,\n configOptions,\n signal: loginController.signal,\n onCredentials: (credentials) => {\n runtimeCredentials = credentials;\n accessKeyAuthCache = null;\n\n appLogger(\n 'Access key loaded for this editor session. Insert it in your .env file to keep it:'\n );\n appLogger(\n `${colorize('INTLAYER_CLIENT_ID=', ANSIColors.GREY_LIGHT)}${colorize(credentials.clientId, ANSIColors.BLUE)}`\n );\n appLogger(\n `${colorize('INTLAYER_CLIENT_SECRET=', ANSIColors.GREY_LIGHT)}${colorize(credentials.clientSecret, ANSIColors.BLUE)}`\n );\n },\n }).finally(() => {\n clearTimeout(timeout);\n\n if (pendingLoginController === loginController) {\n pendingLoginController = null;\n }\n });\n};\n"],"mappings":"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"}
@@ -0,0 +1,41 @@
1
+ import { OrganizationAPI } from "@intlayer/backend-contract/organization";
2
+ import { ProjectAPI } from "@intlayer/backend-contract/project";
3
+ import { UserAPI } from "@intlayer/backend-contract/user";
4
+ import { GetConfigurationOptions } from "@intlayer/config/node";
5
+ import { IntlayerConfig } from "@intlayer/types/config";
6
+ /**
7
+ * Identity the editor client uses to call the CMS, sent as a bearer token.
8
+ *
9
+ * - `session`: the user's short-lived `intlayer login` session token
10
+ * - `accessKey`: an access token exchanged from the project access key
11
+ */
12
+ export type EditorAuth = {
13
+ accessToken: string;
14
+ /** ISO date, `null` when the token never expires. */
15
+ expiresAt: string | null;
16
+ authType: 'session' | 'accessKey';
17
+ user?: UserAPI | null;
18
+ organization?: OrganizationAPI | null;
19
+ project?: ProjectAPI | null;
20
+ };
21
+ /**
22
+ * Resolves the CMS identity of the editor, in the same order as the CLI:
23
+ * `intlayer login` session first, then the access key.
24
+ */
25
+ export declare const resolveEditorAuth: (configuration: IntlayerConfig) => Promise<EditorAuth | null>;
26
+ /**
27
+ * Signs the editor out: drops the `intlayer login` session (shared with the
28
+ * CLI), the pending login and the access key loaded during this run. An access
29
+ * key set in the configuration stays in use, as for the CLI.
30
+ */
31
+ export declare const logoutEditor: (configuration: IntlayerConfig) => Promise<void>;
32
+ /** Whether an editor-triggered login is waiting for the browser callback. */
33
+ export declare const isEditorLoginPending: () => boolean;
34
+ /**
35
+ * Starts the `intlayer login` browser flow. A login still pending is dropped
36
+ * first, so a closed login tab can be reopened. Resolves once the CMS has
37
+ * called back (the session token being stored for both the editor and the
38
+ * CLI), or when the login is dropped or times out.
39
+ */
40
+ export declare const startEditorLogin: (configuration: IntlayerConfig, configOptions?: GetConfigurationOptions) => Promise<void>;
41
+ //# sourceMappingURL=editorAuth.service.d.ts.map
@@ -0,0 +1,136 @@
1
+ import * as ANSIColors from "@intlayer/config/colors";
2
+ import { colorize, getAppLogger } from "@intlayer/config/logger";
3
+ import { getIntlayerAPIProxy, getOAuthAPI } from "@intlayer/api";
4
+ import { clearCliSessionToken, login, readCliSessionToken } from "@intlayer/cli";
5
+
6
+ // src/services/editorAuth.service.ts
7
+ /** Time after which an unanswered login is given up. */
8
+ const LOGIN_TIMEOUT_MS = 3e5;
9
+ /** Margin before expiry under which a cached access token is renewed. */
10
+ const TOKEN_RENEWAL_MARGIN_MS = 6e4;
11
+ /** Credentials picked during an editor-triggered login, kept for this run. */
12
+ let runtimeCredentials = null;
13
+ /** Access token exchanged from the access key, reused until it expires. */
14
+ let accessKeyAuthCache = null;
15
+ /** Identity bound to each CLI session token, resolved once per token. */
16
+ const sessionIdentityCache = /* @__PURE__ */ new Map();
17
+ /** Aborts the login flow waiting for the browser callback, if any. */
18
+ let pendingLoginController = null;
19
+ const isExpiringSoon = (expiresAt) => expiresAt !== null && new Date(expiresAt).getTime() - TOKEN_RENEWAL_MARGIN_MS <= Date.now();
20
+ const getCredentials = (configuration) => {
21
+ if (runtimeCredentials) return runtimeCredentials;
22
+ const { clientId, clientSecret } = configuration.editor;
23
+ return clientId && clientSecret ? {
24
+ clientId,
25
+ clientSecret
26
+ } : null;
27
+ };
28
+ /**
29
+ * Resolves the `intlayer login` session token, shared with the CLI through
30
+ * the temp directory. `null` when absent, expired, or rejected by the CMS.
31
+ */
32
+ const resolveSessionAuth = async (configuration) => {
33
+ const sessionData = await readCliSessionToken(configuration);
34
+ if (!sessionData) return null;
35
+ if (!sessionIdentityCache.has(sessionData.token)) try {
36
+ const result = await getIntlayerAPIProxy(void 0, configuration, sessionData.token).oAuth.getCliSessionMe();
37
+ sessionIdentityCache.set(sessionData.token, {
38
+ user: result.data?.user ?? null,
39
+ organization: result.data?.organization ?? null,
40
+ project: result.data?.project ?? null
41
+ });
42
+ } catch {
43
+ return null;
44
+ }
45
+ return {
46
+ accessToken: sessionData.token,
47
+ expiresAt: sessionData.expiresAt,
48
+ authType: "session",
49
+ ...sessionIdentityCache.get(sessionData.token)
50
+ };
51
+ };
52
+ /**
53
+ * Exchanges the access key for an access token. The client secret stays on
54
+ * the server: only the resulting short-lived token reaches the browser.
55
+ */
56
+ const resolveAccessKeyAuth = async (configuration) => {
57
+ const credentials = getCredentials(configuration);
58
+ if (!credentials) return null;
59
+ if (accessKeyAuthCache?.clientId === credentials.clientId && !isExpiringSoon(accessKeyAuthCache.expiresAt)) {
60
+ const { clientId: _clientId, ...cachedAuth } = accessKeyAuthCache;
61
+ return cachedAuth;
62
+ }
63
+ try {
64
+ const token = (await getOAuthAPI(void 0, { editor: {
65
+ ...configuration.editor,
66
+ ...credentials
67
+ } }).getOAuth2AccessToken()).data;
68
+ if (!token?.accessToken) return null;
69
+ const accessKeyAuth = {
70
+ accessToken: token.accessToken,
71
+ expiresAt: token.accessTokenExpiresAt ? new Date(token.accessTokenExpiresAt).toISOString() : null,
72
+ authType: "accessKey",
73
+ user: token.user,
74
+ organization: token.organization,
75
+ project: token.project
76
+ };
77
+ accessKeyAuthCache = {
78
+ ...accessKeyAuth,
79
+ clientId: credentials.clientId
80
+ };
81
+ return accessKeyAuth;
82
+ } catch {
83
+ accessKeyAuthCache = null;
84
+ return null;
85
+ }
86
+ };
87
+ /**
88
+ * Resolves the CMS identity of the editor, in the same order as the CLI:
89
+ * `intlayer login` session first, then the access key.
90
+ */
91
+ const resolveEditorAuth = async (configuration) => await resolveSessionAuth(configuration) ?? await resolveAccessKeyAuth(configuration);
92
+ /**
93
+ * Signs the editor out: drops the `intlayer login` session (shared with the
94
+ * CLI), the pending login and the access key loaded during this run. An access
95
+ * key set in the configuration stays in use, as for the CLI.
96
+ */
97
+ const logoutEditor = async (configuration) => {
98
+ pendingLoginController?.abort();
99
+ runtimeCredentials = null;
100
+ accessKeyAuthCache = null;
101
+ sessionIdentityCache.clear();
102
+ await clearCliSessionToken(configuration);
103
+ };
104
+ /** Whether an editor-triggered login is waiting for the browser callback. */
105
+ const isEditorLoginPending = () => pendingLoginController !== null;
106
+ /**
107
+ * Starts the `intlayer login` browser flow. A login still pending is dropped
108
+ * first, so a closed login tab can be reopened. Resolves once the CMS has
109
+ * called back (the session token being stored for both the editor and the
110
+ * CLI), or when the login is dropped or times out.
111
+ */
112
+ const startEditorLogin = async (configuration, configOptions) => {
113
+ pendingLoginController?.abort();
114
+ const appLogger = getAppLogger(configuration);
115
+ const loginController = new AbortController();
116
+ const timeout = setTimeout(() => loginController.abort(), LOGIN_TIMEOUT_MS);
117
+ pendingLoginController = loginController;
118
+ await login({
119
+ exitAfter: false,
120
+ configOptions,
121
+ signal: loginController.signal,
122
+ onCredentials: (credentials) => {
123
+ runtimeCredentials = credentials;
124
+ accessKeyAuthCache = null;
125
+ appLogger("Access key loaded for this editor session. Insert it in your .env file to keep it:");
126
+ appLogger(`${colorize("INTLAYER_CLIENT_ID=", ANSIColors.GREY_LIGHT)}${colorize(credentials.clientId, ANSIColors.BLUE)}`);
127
+ appLogger(`${colorize("INTLAYER_CLIENT_SECRET=", ANSIColors.GREY_LIGHT)}${colorize(credentials.clientSecret, ANSIColors.BLUE)}`);
128
+ }
129
+ }).finally(() => {
130
+ clearTimeout(timeout);
131
+ if (pendingLoginController === loginController) pendingLoginController = null;
132
+ });
133
+ };
134
+
135
+ export { isEditorLoginPending, logoutEditor, resolveEditorAuth, startEditorLogin };
136
+ //# sourceMappingURL=editorAuth.service.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"editorAuth.service.mjs","names":[],"sources":["../../src/services/editorAuth.service.ts"],"sourcesContent":["import { getIntlayerAPIProxy, getOAuthAPI } from '@intlayer/api';\nimport type { OrganizationAPI } from '@intlayer/backend-contract/organization';\nimport type { ProjectAPI } from '@intlayer/backend-contract/project';\nimport type { UserAPI } from '@intlayer/backend-contract/user';\nimport {\n clearCliSessionToken,\n login,\n readCliSessionToken,\n} from '@intlayer/cli';\nimport * as ANSIColors from '@intlayer/config/colors';\nimport { colorize, getAppLogger } from '@intlayer/config/logger';\nimport type { GetConfigurationOptions } from '@intlayer/config/node';\nimport type { IntlayerConfig } from '@intlayer/types/config';\n\n/** Access-key credentials used for the client-credentials exchange. */\ntype EditorCredentials = {\n clientId: string;\n clientSecret: string;\n};\n\n/**\n * Identity the editor client uses to call the CMS, sent as a bearer token.\n *\n * - `session`: the user's short-lived `intlayer login` session token\n * - `accessKey`: an access token exchanged from the project access key\n */\nexport type EditorAuth = {\n accessToken: string;\n /** ISO date, `null` when the token never expires. */\n expiresAt: string | null;\n authType: 'session' | 'accessKey';\n user?: UserAPI | null;\n organization?: OrganizationAPI | null;\n project?: ProjectAPI | null;\n};\n\n/** Time after which an unanswered login is given up. */\nconst LOGIN_TIMEOUT_MS = 5 * 60 * 1000;\n\n/** Margin before expiry under which a cached access token is renewed. */\nconst TOKEN_RENEWAL_MARGIN_MS = 60 * 1000;\n\n/** Credentials picked during an editor-triggered login, kept for this run. */\nlet runtimeCredentials: EditorCredentials | null = null;\n\n/** Access token exchanged from the access key, reused until it expires. */\nlet accessKeyAuthCache: (EditorAuth & { clientId: string }) | null = null;\n\n/** Identity bound to a CLI session token. */\ntype SessionIdentity = Pick<EditorAuth, 'user' | 'organization' | 'project'>;\n\n/** Identity bound to each CLI session token, resolved once per token. */\nconst sessionIdentityCache = new Map<string, SessionIdentity>();\n\n/** Aborts the login flow waiting for the browser callback, if any. */\nlet pendingLoginController: AbortController | null = null;\n\nconst isExpiringSoon = (expiresAt: string | null): boolean =>\n expiresAt !== null &&\n new Date(expiresAt).getTime() - TOKEN_RENEWAL_MARGIN_MS <= Date.now();\n\nconst getCredentials = (\n configuration: IntlayerConfig\n): EditorCredentials | null => {\n if (runtimeCredentials) return runtimeCredentials;\n\n const { clientId, clientSecret } = configuration.editor;\n\n return clientId && clientSecret ? { clientId, clientSecret } : null;\n};\n\n/**\n * Resolves the `intlayer login` session token, shared with the CLI through\n * the temp directory. `null` when absent, expired, or rejected by the CMS.\n */\nconst resolveSessionAuth = async (\n configuration: IntlayerConfig\n): Promise<EditorAuth | null> => {\n const sessionData = await readCliSessionToken(configuration);\n\n if (!sessionData) return null;\n\n if (!sessionIdentityCache.has(sessionData.token)) {\n try {\n const result = await getIntlayerAPIProxy(\n undefined,\n configuration,\n sessionData.token\n ).oAuth.getCliSessionMe();\n\n sessionIdentityCache.set(sessionData.token, {\n user: result.data?.user ?? null,\n organization: result.data?.organization ?? null,\n project: result.data?.project ?? null,\n });\n } catch {\n // Revoked or unreachable: fall back to the access key, if any\n return null;\n }\n }\n\n return {\n accessToken: sessionData.token,\n expiresAt: sessionData.expiresAt,\n authType: 'session',\n ...sessionIdentityCache.get(sessionData.token),\n };\n};\n\n/**\n * Exchanges the access key for an access token. The client secret stays on\n * the server: only the resulting short-lived token reaches the browser.\n */\nconst resolveAccessKeyAuth = async (\n configuration: IntlayerConfig\n): Promise<EditorAuth | null> => {\n const credentials = getCredentials(configuration);\n\n if (!credentials) return null;\n\n if (\n accessKeyAuthCache?.clientId === credentials.clientId &&\n !isExpiringSoon(accessKeyAuthCache.expiresAt)\n ) {\n const { clientId: _clientId, ...cachedAuth } = accessKeyAuthCache;\n return cachedAuth;\n }\n\n try {\n const result = await getOAuthAPI(undefined, {\n editor: { ...configuration.editor, ...credentials },\n }).getOAuth2AccessToken();\n\n const token = result.data;\n\n if (!token?.accessToken) return null;\n\n const accessKeyAuth: EditorAuth = {\n accessToken: token.accessToken,\n expiresAt: token.accessTokenExpiresAt\n ? new Date(token.accessTokenExpiresAt).toISOString()\n : null,\n authType: 'accessKey',\n user: token.user,\n organization: token.organization,\n project: token.project,\n };\n\n accessKeyAuthCache = { ...accessKeyAuth, clientId: credentials.clientId };\n\n return accessKeyAuth;\n } catch {\n accessKeyAuthCache = null;\n return null;\n }\n};\n\n/**\n * Resolves the CMS identity of the editor, in the same order as the CLI:\n * `intlayer login` session first, then the access key.\n */\nexport const resolveEditorAuth = async (\n configuration: IntlayerConfig\n): Promise<EditorAuth | null> =>\n (await resolveSessionAuth(configuration)) ??\n (await resolveAccessKeyAuth(configuration));\n\n/**\n * Signs the editor out: drops the `intlayer login` session (shared with the\n * CLI), the pending login and the access key loaded during this run. An access\n * key set in the configuration stays in use, as for the CLI.\n */\nexport const logoutEditor = async (\n configuration: IntlayerConfig\n): Promise<void> => {\n pendingLoginController?.abort();\n runtimeCredentials = null;\n accessKeyAuthCache = null;\n sessionIdentityCache.clear();\n\n await clearCliSessionToken(configuration);\n};\n\n/** Whether an editor-triggered login is waiting for the browser callback. */\nexport const isEditorLoginPending = (): boolean =>\n pendingLoginController !== null;\n\n/**\n * Starts the `intlayer login` browser flow. A login still pending is dropped\n * first, so a closed login tab can be reopened. Resolves once the CMS has\n * called back (the session token being stored for both the editor and the\n * CLI), or when the login is dropped or times out.\n */\nexport const startEditorLogin = async (\n configuration: IntlayerConfig,\n configOptions?: GetConfigurationOptions\n): Promise<void> => {\n pendingLoginController?.abort();\n\n const appLogger = getAppLogger(configuration);\n const loginController = new AbortController();\n const timeout = setTimeout(() => loginController.abort(), LOGIN_TIMEOUT_MS);\n\n pendingLoginController = loginController;\n\n await login({\n exitAfter: false,\n configOptions,\n signal: loginController.signal,\n onCredentials: (credentials) => {\n runtimeCredentials = credentials;\n accessKeyAuthCache = null;\n\n appLogger(\n 'Access key loaded for this editor session. Insert it in your .env file to keep it:'\n );\n appLogger(\n `${colorize('INTLAYER_CLIENT_ID=', ANSIColors.GREY_LIGHT)}${colorize(credentials.clientId, ANSIColors.BLUE)}`\n );\n appLogger(\n `${colorize('INTLAYER_CLIENT_SECRET=', ANSIColors.GREY_LIGHT)}${colorize(credentials.clientSecret, ANSIColors.BLUE)}`\n );\n },\n }).finally(() => {\n clearTimeout(timeout);\n\n if (pendingLoginController === loginController) {\n pendingLoginController = null;\n }\n });\n};\n"],"mappings":"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"}
@@ -1,9 +1,7 @@
1
- //#region src/types/Routes.d.ts
2
1
  export type Route = {
3
2
  urlModel: string;
4
3
  url: string | ((args: any) => string);
5
4
  method: 'GET' | 'POST' | 'PATCH' | 'PUT' | 'DELETE';
6
5
  };
7
6
  export type Routes = Record<string, Route>;
8
- //#endregion
9
7
  //# sourceMappingURL=Routes.d.ts.map
@@ -1,24 +1,49 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
- const require_runtime = require('../_virtual/_rolldown/runtime.cjs');
2
+ const require_runtime = require("../_virtual/_rolldown/runtime.cjs");
3
3
  let node_net = require("node:net");
4
4
  node_net = require_runtime.__toESM(node_net, 1);
5
5
 
6
- //#region src/utils/checkPortAvailability.ts
7
- const checkPortAvailability = (port) => {
8
- return new Promise((resolve) => {
9
- const server = node_net.default.createServer();
10
- server.once("error", (err) => {
11
- if (err.code === "EADDRINUSE") resolve(false);
12
- else resolve(true);
13
- });
14
- server.once("listening", () => {
15
- server.close();
16
- resolve(true);
17
- });
18
- server.listen(port);
6
+ // src/utils/checkPortAvailability.ts
7
+ /**
8
+ * Resolves `false` when `port` is already bound on `host`.
9
+ *
10
+ * Probe the same host the server listens on: on macOS an IPv6 `::` probe
11
+ * succeeds even when `0.0.0.0:port` is taken.
12
+ */
13
+ const checkPortAvailability = (port, host = "0.0.0.0") => new Promise((resolve) => {
14
+ const server = node_net.default.createServer();
15
+ server.once("error", (error) => {
16
+ resolve(error.code !== "EADDRINUSE");
19
17
  });
18
+ server.once("listening", () => {
19
+ server.close();
20
+ resolve(true);
21
+ });
22
+ server.listen(port, host);
23
+ });
24
+ /**
25
+ * Loopback hosts probed besides the listening one: a server bound to a single
26
+ * loopback address (e.g. a dev server on `::1`) would shadow the editor for
27
+ * `localhost` while leaving `0.0.0.0` free.
28
+ */
29
+ const LOOPBACK_HOSTS = ["127.0.0.1", "::1"];
30
+ /**
31
+ * First port from `startPort` free on `host` and on the loopback addresses,
32
+ * trying the next ones in order.
33
+ *
34
+ * @returns The port, or `undefined` when the `attempts` ports are all taken.
35
+ */
36
+ const findAvailablePort = async (startPort, host = "0.0.0.0", attempts = 20) => {
37
+ for (let port = startPort; port < startPort + attempts; port++) {
38
+ let isAvailable = true;
39
+ for (const probedHost of [host, ...LOOPBACK_HOSTS]) {
40
+ isAvailable = await checkPortAvailability(port, probedHost);
41
+ if (!isAvailable) break;
42
+ }
43
+ if (isAvailable) return port;
44
+ }
20
45
  };
21
46
 
22
- //#endregion
23
47
  exports.checkPortAvailability = checkPortAvailability;
48
+ exports.findAvailablePort = findAvailablePort;
24
49
  //# sourceMappingURL=checkPortAvailability.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"checkPortAvailability.cjs","names":["net"],"sources":["../../src/utils/checkPortAvailability.ts"],"sourcesContent":["import net from 'node:net';\n\nexport const checkPortAvailability = (port: number): Promise<boolean> => {\n return new Promise((resolve) => {\n const server = net.createServer();\n\n server.once('error', (err: any) => {\n if (err.code === 'EADDRINUSE') {\n resolve(false); // Port is in use\n } else {\n resolve(true);\n }\n });\n\n server.once('listening', () => {\n server.close();\n resolve(true); // Port is available\n });\n\n server.listen(port);\n });\n};\n"],"mappings":";;;;;;AAEA,MAAa,yBAAyB,SAAmC;CACvE,OAAO,IAAI,SAAS,YAAY;EAC9B,MAAM,SAASA,iBAAI,aAAa;EAEhC,OAAO,KAAK,UAAU,QAAa;GACjC,IAAI,IAAI,SAAS,cACf,QAAQ,KAAK;QAEb,QAAQ,IAAI;EAEhB,CAAC;EAED,OAAO,KAAK,mBAAmB;GAC7B,OAAO,MAAM;GACb,QAAQ,IAAI;EACd,CAAC;EAED,OAAO,OAAO,IAAI;CACpB,CAAC;AACH"}
1
+ {"version":3,"file":"checkPortAvailability.cjs","names":["net"],"sources":["../../src/utils/checkPortAvailability.ts"],"sourcesContent":["import net from 'node:net';\n\n/**\n * Resolves `false` when `port` is already bound on `host`.\n *\n * Probe the same host the server listens on: on macOS an IPv6 `::` probe\n * succeeds even when `0.0.0.0:port` is taken.\n */\nexport const checkPortAvailability = (\n port: number,\n host = '0.0.0.0'\n): Promise<boolean> =>\n new Promise((resolve) => {\n const server = net.createServer();\n\n server.once('error', (error: NodeJS.ErrnoException) => {\n resolve(error.code !== 'EADDRINUSE');\n });\n\n server.once('listening', () => {\n server.close();\n resolve(true);\n });\n\n server.listen(port, host);\n });\n\n/**\n * Loopback hosts probed besides the listening one: a server bound to a single\n * loopback address (e.g. a dev server on `::1`) would shadow the editor for\n * `localhost` while leaving `0.0.0.0` free.\n */\nconst LOOPBACK_HOSTS = ['127.0.0.1', '::1'] as const;\n\n/**\n * First port from `startPort` free on `host` and on the loopback addresses,\n * trying the next ones in order.\n *\n * @returns The port, or `undefined` when the `attempts` ports are all taken.\n */\nexport const findAvailablePort = async (\n startPort: number,\n host = '0.0.0.0',\n attempts = 20\n): Promise<number | undefined> => {\n for (let port = startPort; port < startPort + attempts; port++) {\n let isAvailable = true;\n\n for (const probedHost of [host, ...LOOPBACK_HOSTS]) {\n isAvailable = await checkPortAvailability(port, probedHost);\n if (!isAvailable) break;\n }\n\n if (isAvailable) return port;\n }\n\n return undefined;\n};\n"],"mappings":";;;;;;;;;;;;AAQA,MAAa,yBACX,MACA,OAAO,cAEP,IAAI,SAAS,YAAY;CACvB,MAAM,SAASA,iBAAI,aAAa;CAEhC,OAAO,KAAK,UAAU,UAAiC;EACrD,QAAQ,MAAM,SAAS,YAAY;CACrC,CAAC;CAED,OAAO,KAAK,mBAAmB;EAC7B,OAAO,MAAM;EACb,QAAQ,IAAI;CACd,CAAC;CAED,OAAO,OAAO,MAAM,IAAI;AAC1B,CAAC;;;;;;AAOH,MAAM,iBAAiB,CAAC,aAAa,KAAK;;;;;;;AAQ1C,MAAa,oBAAoB,OAC/B,WACA,OAAO,WACP,WAAW,OACqB;CAChC,KAAK,IAAI,OAAO,WAAW,OAAO,YAAY,UAAU,QAAQ;EAC9D,IAAI,cAAc;EAElB,KAAK,MAAM,cAAc,CAAC,MAAM,GAAG,cAAc,GAAG;GAClD,cAAc,MAAM,sBAAsB,MAAM,UAAU;GAC1D,IAAI,CAAC,aAAa;EACpB;EAEA,IAAI,aAAa,OAAO;CAC1B;AAGF"}
@@ -1,4 +1,15 @@
1
- //#region src/utils/checkPortAvailability.d.ts
2
- export declare const checkPortAvailability: (port: number) => Promise<boolean>;
3
- //#endregion
1
+ /**
2
+ * Resolves `false` when `port` is already bound on `host`.
3
+ *
4
+ * Probe the same host the server listens on: on macOS an IPv6 `::` probe
5
+ * succeeds even when `0.0.0.0:port` is taken.
6
+ */
7
+ export declare const checkPortAvailability: (port: number, host?: string) => Promise<boolean>;
8
+ /**
9
+ * First port from `startPort` free on `host` and on the loopback addresses,
10
+ * trying the next ones in order.
11
+ *
12
+ * @returns The port, or `undefined` when the `attempts` ports are all taken.
13
+ */
14
+ export declare const findAvailablePort: (startPort: number, host?: string, attempts?: number) => Promise<number | undefined>;
4
15
  //# sourceMappingURL=checkPortAvailability.d.ts.map
@@ -1,21 +1,45 @@
1
1
  import net from "node:net";
2
2
 
3
- //#region src/utils/checkPortAvailability.ts
4
- const checkPortAvailability = (port) => {
5
- return new Promise((resolve) => {
6
- const server = net.createServer();
7
- server.once("error", (err) => {
8
- if (err.code === "EADDRINUSE") resolve(false);
9
- else resolve(true);
10
- });
11
- server.once("listening", () => {
12
- server.close();
13
- resolve(true);
14
- });
15
- server.listen(port);
3
+ // src/utils/checkPortAvailability.ts
4
+ /**
5
+ * Resolves `false` when `port` is already bound on `host`.
6
+ *
7
+ * Probe the same host the server listens on: on macOS an IPv6 `::` probe
8
+ * succeeds even when `0.0.0.0:port` is taken.
9
+ */
10
+ const checkPortAvailability = (port, host = "0.0.0.0") => new Promise((resolve) => {
11
+ const server = net.createServer();
12
+ server.once("error", (error) => {
13
+ resolve(error.code !== "EADDRINUSE");
16
14
  });
15
+ server.once("listening", () => {
16
+ server.close();
17
+ resolve(true);
18
+ });
19
+ server.listen(port, host);
20
+ });
21
+ /**
22
+ * Loopback hosts probed besides the listening one: a server bound to a single
23
+ * loopback address (e.g. a dev server on `::1`) would shadow the editor for
24
+ * `localhost` while leaving `0.0.0.0` free.
25
+ */
26
+ const LOOPBACK_HOSTS = ["127.0.0.1", "::1"];
27
+ /**
28
+ * First port from `startPort` free on `host` and on the loopback addresses,
29
+ * trying the next ones in order.
30
+ *
31
+ * @returns The port, or `undefined` when the `attempts` ports are all taken.
32
+ */
33
+ const findAvailablePort = async (startPort, host = "0.0.0.0", attempts = 20) => {
34
+ for (let port = startPort; port < startPort + attempts; port++) {
35
+ let isAvailable = true;
36
+ for (const probedHost of [host, ...LOOPBACK_HOSTS]) {
37
+ isAvailable = await checkPortAvailability(port, probedHost);
38
+ if (!isAvailable) break;
39
+ }
40
+ if (isAvailable) return port;
41
+ }
17
42
  };
18
43
 
19
- //#endregion
20
- export { checkPortAvailability };
44
+ export { checkPortAvailability, findAvailablePort };
21
45
  //# sourceMappingURL=checkPortAvailability.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"checkPortAvailability.mjs","names":[],"sources":["../../src/utils/checkPortAvailability.ts"],"sourcesContent":["import net from 'node:net';\n\nexport const checkPortAvailability = (port: number): Promise<boolean> => {\n return new Promise((resolve) => {\n const server = net.createServer();\n\n server.once('error', (err: any) => {\n if (err.code === 'EADDRINUSE') {\n resolve(false); // Port is in use\n } else {\n resolve(true);\n }\n });\n\n server.once('listening', () => {\n server.close();\n resolve(true); // Port is available\n });\n\n server.listen(port);\n });\n};\n"],"mappings":";;;AAEA,MAAa,yBAAyB,SAAmC;CACvE,OAAO,IAAI,SAAS,YAAY;EAC9B,MAAM,SAAS,IAAI,aAAa;EAEhC,OAAO,KAAK,UAAU,QAAa;GACjC,IAAI,IAAI,SAAS,cACf,QAAQ,KAAK;QAEb,QAAQ,IAAI;EAEhB,CAAC;EAED,OAAO,KAAK,mBAAmB;GAC7B,OAAO,MAAM;GACb,QAAQ,IAAI;EACd,CAAC;EAED,OAAO,OAAO,IAAI;CACpB,CAAC;AACH"}
1
+ {"version":3,"file":"checkPortAvailability.mjs","names":[],"sources":["../../src/utils/checkPortAvailability.ts"],"sourcesContent":["import net from 'node:net';\n\n/**\n * Resolves `false` when `port` is already bound on `host`.\n *\n * Probe the same host the server listens on: on macOS an IPv6 `::` probe\n * succeeds even when `0.0.0.0:port` is taken.\n */\nexport const checkPortAvailability = (\n port: number,\n host = '0.0.0.0'\n): Promise<boolean> =>\n new Promise((resolve) => {\n const server = net.createServer();\n\n server.once('error', (error: NodeJS.ErrnoException) => {\n resolve(error.code !== 'EADDRINUSE');\n });\n\n server.once('listening', () => {\n server.close();\n resolve(true);\n });\n\n server.listen(port, host);\n });\n\n/**\n * Loopback hosts probed besides the listening one: a server bound to a single\n * loopback address (e.g. a dev server on `::1`) would shadow the editor for\n * `localhost` while leaving `0.0.0.0` free.\n */\nconst LOOPBACK_HOSTS = ['127.0.0.1', '::1'] as const;\n\n/**\n * First port from `startPort` free on `host` and on the loopback addresses,\n * trying the next ones in order.\n *\n * @returns The port, or `undefined` when the `attempts` ports are all taken.\n */\nexport const findAvailablePort = async (\n startPort: number,\n host = '0.0.0.0',\n attempts = 20\n): Promise<number | undefined> => {\n for (let port = startPort; port < startPort + attempts; port++) {\n let isAvailable = true;\n\n for (const probedHost of [host, ...LOOPBACK_HOSTS]) {\n isAvailable = await checkPortAvailability(port, probedHost);\n if (!isAvailable) break;\n }\n\n if (isAvailable) return port;\n }\n\n return undefined;\n};\n"],"mappings":";;;;;;;;;AAQA,MAAa,yBACX,MACA,OAAO,cAEP,IAAI,SAAS,YAAY;CACvB,MAAM,SAAS,IAAI,aAAa;CAEhC,OAAO,KAAK,UAAU,UAAiC;EACrD,QAAQ,MAAM,SAAS,YAAY;CACrC,CAAC;CAED,OAAO,KAAK,mBAAmB;EAC7B,OAAO,MAAM;EACb,QAAQ,IAAI;CACd,CAAC;CAED,OAAO,OAAO,MAAM,IAAI;AAC1B,CAAC;;;;;;AAOH,MAAM,iBAAiB,CAAC,aAAa,KAAK;;;;;;;AAQ1C,MAAa,oBAAoB,OAC/B,WACA,OAAO,WACP,WAAW,OACqB;CAChC,KAAK,IAAI,OAAO,WAAW,OAAO,YAAY,UAAU,QAAQ;EAC9D,IAAI,cAAc;EAElB,KAAK,MAAM,cAAc,CAAC,MAAM,GAAG,cAAc,GAAG;GAClD,cAAc,MAAM,sBAAsB,MAAM,UAAU;GAC1D,IAAI,CAAC,aAAa;EACpB;EAEA,IAAI,aAAa,OAAO;CAC1B;AAGF"}
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
- //#region src/utils/httpStatusCodes.ts
2
+ // src/utils/httpStatusCodes.ts
3
3
  /**
4
4
  * Enum for HTTP response status codes.
5
5
  * Contains all the possible HTTP response codes according to the standard.
@@ -396,6 +396,5 @@ let HttpStatusCodes = /* @__PURE__ */ function(HttpStatusCodes) {
396
396
  return HttpStatusCodes;
397
397
  }({});
398
398
 
399
- //#endregion
400
399
  exports.HttpStatusCodes = HttpStatusCodes;
401
400
  //# sourceMappingURL=httpStatusCodes.cjs.map
@@ -1,4 +1,3 @@
1
- //#region src/utils/httpStatusCodes.d.ts
2
1
  /**
3
2
  * Enum for HTTP response status codes.
4
3
  * Contains all the possible HTTP response codes according to the standard.
@@ -155,8 +154,7 @@ export declare enum HttpStatusCodes {
155
154
  /**
156
155
  * Used to return some response headers before final HTTP message.
157
156
  */
158
- PROCESSING_102 = 102 // WebDAV
159
- ,
157
+ PROCESSING_102 = 102, // WebDAV
160
158
  /**
161
159
  * Early hints - part of optimization to improve page load time.
162
160
  */
@@ -192,13 +190,11 @@ export declare enum HttpStatusCodes {
192
190
  /**
193
191
  * Multi-status response providing status for multiple independent operations.
194
192
  */
195
- MULTI_STATUS_207 = 207 // WebDAV
196
- ,
193
+ MULTI_STATUS_207 = 207, // WebDAV
197
194
  /**
198
195
  * The members of a DAV binding have already been enumerated in a preceding part of the request.
199
196
  */
200
- ALREADY_REPORTED_208 = 208 // WebDAV
201
- ,
197
+ ALREADY_REPORTED_208 = 208, // WebDAV
202
198
  /**
203
199
  * The server has fulfilled a GET request for the resource, and the response is a representation of the result of one or more instance manipulations applied to the current instance.
204
200
  */
@@ -310,8 +306,7 @@ export declare enum HttpStatusCodes {
310
306
  /**
311
307
  * The server refuses the attempt to brew coffee with a teapot.
312
308
  */
313
- IM_A_TEAPOT_418 = 418 // Easter egg from the HTCPCP/1.0 protocol.
314
- ,
309
+ IM_A_TEAPOT_418 = 418, // Easter egg from the HTCPCP/1.0 protocol.
315
310
  /**
316
311
  * The request was directed at a server that is not able to produce a response.
317
312
  */
@@ -319,18 +314,15 @@ export declare enum HttpStatusCodes {
319
314
  /**
320
315
  * The request was well-formed but was unable to be followed due to semantic errors.
321
316
  */
322
- UNPROCESSABLE_ENTITY_422 = 422 // WebDAV
323
- ,
317
+ UNPROCESSABLE_ENTITY_422 = 422, // WebDAV
324
318
  /**
325
319
  * The resource that is being accessed is locked.
326
320
  */
327
- LOCKED_423 = 423 // WebDAV
328
- ,
321
+ LOCKED_423 = 423, // WebDAV
329
322
  /**
330
323
  * The request failed due to failure of a previous request.
331
324
  */
332
- FAILED_DEPENDENCY_424 = 424 // WebDAV
333
- ,
325
+ FAILED_DEPENDENCY_424 = 424, // WebDAV
334
326
  /**
335
327
  * Indicates that the server is unwilling to risk processing a request that might be replayed.
336
328
  */
@@ -386,13 +378,11 @@ export declare enum HttpStatusCodes {
386
378
  /**
387
379
  * The server is unable to store the representation needed to complete the request.
388
380
  */
389
- INSUFFICIENT_STORAGE_507 = 507 // WebDAV
390
- ,
381
+ INSUFFICIENT_STORAGE_507 = 507, // WebDAV
391
382
  /**
392
383
  * The server detected an infinite loop while processing a request.
393
384
  */
394
- LOOP_DETECTED_508 = 508 // WebDAV
395
- ,
385
+ LOOP_DETECTED_508 = 508, // WebDAV
396
386
  /**
397
387
  * Further extensions to the request are required for the server to fulfill it.
398
388
  */
@@ -402,5 +392,4 @@ export declare enum HttpStatusCodes {
402
392
  */
403
393
  NETWORK_AUTHENTICATION_REQUIRED_511 = 511
404
394
  }
405
- //#endregion
406
395
  //# sourceMappingURL=httpStatusCodes.d.ts.map