@intlayer/cli 9.5.12 → 9.7.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.
- package/dist/cjs/IntlayerEventListener.cjs.map +1 -1
- package/dist/cjs/_virtual/_utils_asset.cjs +1 -1
- package/dist/cjs/auth/login.cjs +11 -0
- package/dist/cjs/auth/login.cjs.map +1 -1
- package/dist/cjs/auth/setupRepository.cjs +95 -0
- package/dist/cjs/auth/setupRepository.cjs.map +1 -0
- package/dist/cjs/ci.cjs +1 -1
- package/dist/cjs/cli.cjs +59 -14
- package/dist/cjs/cli.cjs.map +1 -1
- package/dist/cjs/editor.cjs +61 -32
- package/dist/cjs/editor.cjs.map +1 -1
- package/dist/cjs/extract.cjs +1 -1
- package/dist/cjs/fill/fill.cjs +25 -9
- package/dist/cjs/fill/fill.cjs.map +1 -1
- package/dist/cjs/fill/translateDictionary.cjs.map +1 -1
- package/dist/cjs/getTargetDictionary.cjs +1 -1
- package/dist/cjs/index.cjs +29 -1
- package/dist/cjs/init.cjs +261 -36
- package/dist/cjs/init.cjs.map +1 -1
- package/dist/cjs/initChromeExtension.cjs +26 -3
- package/dist/cjs/initChromeExtension.cjs.map +1 -1
- package/dist/cjs/initInfra.cjs +6 -0
- package/dist/cjs/initInfra.cjs.map +1 -1
- package/dist/cjs/initMCP.cjs +18 -7
- package/dist/cjs/initMCP.cjs.map +1 -1
- package/dist/cjs/initSkills.cjs +37 -34
- package/dist/cjs/initSkills.cjs.map +1 -1
- package/dist/cjs/listContentDeclaration.cjs +1 -1
- package/dist/cjs/listProjects.cjs +1 -1
- package/dist/cjs/liveSync.cjs.map +1 -1
- package/dist/cjs/pull.cjs +1 -1
- package/dist/cjs/push/push.cjs +1 -1
- package/dist/cjs/reviewDoc/reviewDoc.cjs +1 -1
- package/dist/cjs/reviewDoc/reviewDoc.cjs.map +1 -1
- package/dist/cjs/reviewDoc/reviewDocBlockAware.cjs +1 -1
- package/dist/cjs/reviewDoc/reviewDocBlockAware.cjs.map +1 -1
- package/dist/cjs/translateDoc/translateDoc.cjs +1 -1
- package/dist/cjs/upgrade.cjs +1 -1
- package/dist/cjs/utils/checkAccess.cjs.map +1 -1
- package/dist/cjs/utils/detectGitRepository.cjs +87 -0
- package/dist/cjs/utils/detectGitRepository.cjs.map +1 -0
- package/dist/cjs/utils/getParentPackageJSON.cjs +1 -1
- package/dist/cjs/utils/isInteractiveTerminal.cjs +12 -0
- package/dist/cjs/utils/isInteractiveTerminal.cjs.map +1 -0
- package/dist/cjs/utils/parseChoice.cjs +21 -0
- package/dist/cjs/utils/parseChoice.cjs.map +1 -0
- package/dist/esm/IntlayerEventListener.mjs.map +1 -1
- package/dist/esm/_virtual/_utils_asset.mjs +1 -1
- package/dist/esm/auth/login.mjs +11 -0
- package/dist/esm/auth/login.mjs.map +1 -1
- package/dist/esm/auth/setupRepository.mjs +90 -0
- package/dist/esm/auth/setupRepository.mjs.map +1 -0
- package/dist/esm/ci.mjs +1 -1
- package/dist/esm/cli.mjs +59 -14
- package/dist/esm/cli.mjs.map +1 -1
- package/dist/esm/editor.mjs +62 -34
- package/dist/esm/editor.mjs.map +1 -1
- package/dist/esm/extract.mjs +1 -1
- package/dist/esm/fill/fill.mjs +25 -9
- package/dist/esm/fill/fill.mjs.map +1 -1
- package/dist/esm/fill/translateDictionary.mjs.map +1 -1
- package/dist/esm/getTargetDictionary.mjs +1 -1
- package/dist/esm/index.mjs +10 -5
- package/dist/esm/init.mjs +253 -38
- package/dist/esm/init.mjs.map +1 -1
- package/dist/esm/initChromeExtension.mjs +24 -4
- package/dist/esm/initChromeExtension.mjs.map +1 -1
- package/dist/esm/initInfra.mjs +6 -0
- package/dist/esm/initInfra.mjs.map +1 -1
- package/dist/esm/initMCP.mjs +21 -11
- package/dist/esm/initMCP.mjs.map +1 -1
- package/dist/esm/initSkills.mjs +37 -36
- package/dist/esm/initSkills.mjs.map +1 -1
- package/dist/esm/listContentDeclaration.mjs +1 -1
- package/dist/esm/listProjects.mjs +1 -1
- package/dist/esm/liveSync.mjs.map +1 -1
- package/dist/esm/pull.mjs +1 -1
- package/dist/esm/push/push.mjs +1 -1
- package/dist/esm/reviewDoc/reviewDoc.mjs +1 -1
- package/dist/esm/reviewDoc/reviewDoc.mjs.map +1 -1
- package/dist/esm/reviewDoc/reviewDocBlockAware.mjs +1 -1
- package/dist/esm/reviewDoc/reviewDocBlockAware.mjs.map +1 -1
- package/dist/esm/translateDoc/translateDoc.mjs +1 -1
- package/dist/esm/upgrade.mjs +1 -1
- package/dist/esm/utils/checkAccess.mjs.map +1 -1
- package/dist/esm/utils/detectGitRepository.mjs +85 -0
- package/dist/esm/utils/detectGitRepository.mjs.map +1 -0
- package/dist/esm/utils/getParentPackageJSON.mjs +1 -1
- package/dist/esm/utils/isInteractiveTerminal.mjs +11 -0
- package/dist/esm/utils/isInteractiveTerminal.mjs.map +1 -0
- package/dist/esm/utils/parseChoice.mjs +20 -0
- package/dist/esm/utils/parseChoice.mjs.map +1 -0
- package/dist/types/IntlayerEventListener.d.ts +1 -1
- package/dist/types/IntlayerEventListener.d.ts.map +1 -1
- package/dist/types/auth/login.d.ts +5 -0
- package/dist/types/auth/login.d.ts.map +1 -1
- package/dist/types/auth/setupRepository.d.ts +25 -0
- package/dist/types/auth/setupRepository.d.ts.map +1 -0
- package/dist/types/editor.d.ts +10 -0
- package/dist/types/editor.d.ts.map +1 -1
- package/dist/types/fill/fill.d.ts +23 -2
- package/dist/types/fill/fill.d.ts.map +1 -1
- package/dist/types/fill/index.d.ts +2 -2
- package/dist/types/fill/translateDictionary.d.ts +1 -2
- package/dist/types/fill/translateDictionary.d.ts.map +1 -1
- package/dist/types/index.d.ts +11 -6
- package/dist/types/init.d.ts +55 -2
- package/dist/types/init.d.ts.map +1 -1
- package/dist/types/initChromeExtension.d.ts +19 -1
- package/dist/types/initChromeExtension.d.ts.map +1 -1
- package/dist/types/initInfra.d.ts.map +1 -1
- package/dist/types/initMCP.d.ts +16 -2
- package/dist/types/initMCP.d.ts.map +1 -1
- package/dist/types/initSkills.d.ts +23 -6
- package/dist/types/initSkills.d.ts.map +1 -1
- package/dist/types/liveSync.d.ts.map +1 -1
- package/dist/types/reviewDoc/reviewDoc.d.ts +1 -1
- package/dist/types/reviewDoc/reviewDoc.d.ts.map +1 -1
- package/dist/types/reviewDoc/reviewDocBlockAware.d.ts +1 -2
- package/dist/types/reviewDoc/reviewDocBlockAware.d.ts.map +1 -1
- package/dist/types/translateDoc/types.d.ts +1 -1
- package/dist/types/utils/checkAccess.d.ts +2 -1
- package/dist/types/utils/checkAccess.d.ts.map +1 -1
- package/dist/types/utils/detectGitRepository.d.ts +34 -0
- package/dist/types/utils/detectGitRepository.d.ts.map +1 -0
- package/dist/types/utils/isInteractiveTerminal.d.ts +9 -0
- package/dist/types/utils/isInteractiveTerminal.d.ts.map +1 -0
- package/dist/types/utils/parseChoice.d.ts +13 -0
- package/dist/types/utils/parseChoice.d.ts.map +1 -0
- package/dist/types/utils/selectCmsEnvironment.d.ts +1 -1
- package/package.json +12 -12
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"IntlayerEventListener.cjs","names":["getAppLogger","getIntlayerAPIProxy","EventSource"],"sources":["../../src/IntlayerEventListener.ts"],"sourcesContent":["import { getIntlayerAPIProxy } from '@intlayer/api';\n// @ts-ignore: @intlayer/backend is not built yet\nimport type { DictionaryAPI, MessageEventData } from '@intlayer/backend';\nimport { editor, log } from '@intlayer/config/built';\nimport { getAppLogger } from '@intlayer/config/logger';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport { EventSource } from 'eventsource';\n\nexport type IntlayerMessageEvent = MessageEvent;\n\n/**\n * IntlayerEventListener class to listen for dictionary changes via SSE (Server-Sent Events).\n *\n * Usage example:\n *\n * import { buildIntlayerDictionary } from './transpiler/declaration_file_to_dictionary/intlayer_dictionary';\n * import { IntlayerEventListener } from '@intlayer/api';\n *\n * export const checkDictionaryChanges = async () => {\n * // Instantiate the listener\n * const eventListener = new IntlayerEventListener();\n *\n * // Set up your callbacks\n * eventListener.onDictionaryChange = async (dictionary) => {\n * await buildIntlayerDictionary(dictionary);\n * };\n *\n * // Initialize the listener\n * await eventListener.initialize();\n *\n * // Optionally, clean up later when you’re done\n * // eventListener.cleanup();\n * };\n */\nexport class IntlayerEventListener {\n private appLogger = getAppLogger({ log });\n\n private eventSource: EventSource | null = null;\n private reconnectAttempts = 0;\n private maxReconnectAttempts = 5;\n private reconnectDelay = 1000; // Start with 1 second\n private isManuallyDisconnected = false;\n private reconnectTimeout: NodeJS.Timeout | null = null;\n\n /**\n * Callback triggered when a Dictionary is ADDED.\n */\n public onDictionaryAdded?: (dictionary: DictionaryAPI) => any;\n\n /**\n * Callback triggered when a Dictionary is UPDATED.\n */\n public onDictionaryChange?: (dictionary: DictionaryAPI) => any;\n\n /**\n * Callback triggered when a Dictionary is DELETED.\n */\n public onDictionaryDeleted?: (dictionary: DictionaryAPI) => any;\n\n /**\n * Callback triggered when connection is established or re-established.\n */\n public onConnectionOpen?: () => any;\n\n /**\n * Callback triggered when connection encounters an error.\n */\n public onConnectionError?: (error: Event) => any;\n\n constructor(\n private intlayerConfig: Pick<IntlayerConfig, 'log' | 'editor'> = {\n log,\n editor,\n }\n ) {\n this.appLogger = getAppLogger(this.intlayerConfig);\n }\n\n /**\n * Initializes the EventSource connection using the given intlayerConfig\n * (or the default config if none was provided).\n */\n public async initialize(): Promise<void> {\n this.isManuallyDisconnected = false;\n await this.connect();\n }\n\n /**\n * Establishes the EventSource connection with automatic reconnection support.\n */\n private async connect(): Promise<void> {\n try {\n const backendURL = this.intlayerConfig.editor.backendURL;\n\n // Retrieve the access token via proxy\n const accessToken = await getIntlayerAPIProxy(\n undefined,\n this.intlayerConfig\n ).oAuth.getOAuth2AccessToken();\n\n if (!accessToken) {\n throw new Error('Failed to retrieve access token');\n }\n\n const API_ROUTE = `${backendURL}/api/event-listener`;\n\n // Close existing connection if any\n if (this.eventSource) {\n this.eventSource.close();\n }\n\n this.eventSource = new EventSource(API_ROUTE, {\n fetch: (input, init) =>\n fetch(input, {\n ...init,\n headers: {\n ...(init?.headers ?? {}),\n Authorization: `Bearer ${accessToken.data?.accessToken}`,\n },\n }),\n });\n\n this.eventSource.onopen = () => {\n this.reconnectAttempts = 0;\n this.reconnectDelay = 1000; // Reset delay\n this.onConnectionOpen?.();\n };\n\n this.eventSource.onmessage = (event) => this.handleMessage(event);\n this.eventSource.onerror = (event) => this.handleError(event);\n } catch (_error) {\n this.appLogger('Failed to establish EventSource connection:', {\n level: 'error',\n });\n this.scheduleReconnect();\n }\n }\n\n /**\n * Cleans up (closes) the EventSource connection.\n */\n public cleanup(): void {\n this.isManuallyDisconnected = true;\n\n if (this.reconnectTimeout) {\n clearTimeout(this.reconnectTimeout);\n this.reconnectTimeout = null;\n }\n\n if (this.eventSource) {\n this.eventSource.close();\n this.eventSource = null;\n }\n }\n\n /**\n * Schedules a reconnection attempt with exponential backoff.\n */\n private scheduleReconnect(): void {\n if (\n this.isManuallyDisconnected ||\n this.reconnectAttempts >= this.maxReconnectAttempts\n ) {\n if (this.reconnectAttempts >= this.maxReconnectAttempts) {\n this.appLogger(\n [\n `Max reconnection attempts (${this.maxReconnectAttempts}) reached. Giving up.`,\n ],\n {\n level: 'error',\n }\n );\n }\n return;\n }\n\n this.reconnectAttempts++;\n const delay = this.reconnectDelay * 2 ** (this.reconnectAttempts - 1); // Exponential backoff\n\n this.appLogger(\n `Scheduling reconnection attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts} in ${delay}ms`\n );\n\n this.reconnectTimeout = setTimeout(async () => {\n if (!this.isManuallyDisconnected) {\n await this.connect();\n }\n }, delay);\n }\n\n /**\n * Handles incoming SSE messages, parses the event data,\n * and invokes the appropriate callback.\n */\n private async handleMessage(event: IntlayerMessageEvent): Promise<void> {\n try {\n const { data } = event;\n\n const dataJSON: MessageEventData[] = JSON.parse(data);\n\n for (const dataEl of dataJSON) {\n switch (dataEl.object) {\n case 'DICTIONARY':\n switch (dataEl.status) {\n case 'ADDED':\n await this.onDictionaryAdded?.(dataEl.data);\n break;\n case 'UPDATED':\n await this.onDictionaryChange?.(dataEl.data);\n break;\n case 'DELETED':\n await this.onDictionaryDeleted?.(dataEl.data);\n break;\n default:\n this.appLogger(\n ['Unhandled dictionary status:', dataEl.status],\n {\n level: 'error',\n }\n );\n break;\n }\n break;\n default:\n this.appLogger(['Unknown object type:', dataEl.object], {\n level: 'error',\n });\n break;\n }\n }\n } catch (error) {\n this.appLogger(['Error processing dictionary update:', error], {\n level: 'error',\n });\n }\n }\n\n /**\n * Handles any SSE errors and attempts reconnection if appropriate.\n */\n private handleError(event: Event): void {\n const errorEvent = event as any;\n\n // Log detailed error information\n this.appLogger(\n [\n 'EventSource error:',\n {\n type: errorEvent.type,\n message: errorEvent.message,\n code: errorEvent.code,\n readyState: this.eventSource?.readyState,\n url: this.eventSource?.url,\n },\n ],\n {\n level: 'error',\n }\n );\n\n // Notify error callback\n this.onConnectionError?.(event);\n\n // Check if this is a connection close error\n const isConnectionClosed =\n errorEvent.type === 'error' &&\n (errorEvent.message?.includes('terminated') ||\n errorEvent.message?.includes('closed') ||\n this.eventSource?.readyState === EventSource.CLOSED);\n\n if (isConnectionClosed && !this.isManuallyDisconnected) {\n this.appLogger(\n 'Connection was terminated by server, attempting to reconnect...'\n );\n this.scheduleReconnect();\n } else {\n // For other types of errors, close the connection\n this.cleanup();\n }\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"IntlayerEventListener.cjs","names":["getAppLogger","getIntlayerAPIProxy","EventSource"],"sources":["../../src/IntlayerEventListener.ts"],"sourcesContent":["import type { DictionaryAPI, MessageEventData } from '@intlayer/api';\nimport { getIntlayerAPIProxy } from '@intlayer/api';\nimport { editor, log } from '@intlayer/config/built';\nimport { getAppLogger } from '@intlayer/config/logger';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport { EventSource } from 'eventsource';\n\nexport type IntlayerMessageEvent = MessageEvent;\n\n/**\n * IntlayerEventListener class to listen for dictionary changes via SSE (Server-Sent Events).\n *\n * Usage example:\n *\n * import { buildIntlayerDictionary } from './transpiler/declaration_file_to_dictionary/intlayer_dictionary';\n * import { IntlayerEventListener } from '@intlayer/api';\n *\n * export const checkDictionaryChanges = async () => {\n * // Instantiate the listener\n * const eventListener = new IntlayerEventListener();\n *\n * // Set up your callbacks\n * eventListener.onDictionaryChange = async (dictionary) => {\n * await buildIntlayerDictionary(dictionary);\n * };\n *\n * // Initialize the listener\n * await eventListener.initialize();\n *\n * // Optionally, clean up later when you’re done\n * // eventListener.cleanup();\n * };\n */\nexport class IntlayerEventListener {\n private appLogger = getAppLogger({ log });\n\n private eventSource: EventSource | null = null;\n private reconnectAttempts = 0;\n private maxReconnectAttempts = 5;\n private reconnectDelay = 1000; // Start with 1 second\n private isManuallyDisconnected = false;\n private reconnectTimeout: NodeJS.Timeout | null = null;\n\n /**\n * Callback triggered when a Dictionary is ADDED.\n */\n public onDictionaryAdded?: (dictionary: DictionaryAPI) => any;\n\n /**\n * Callback triggered when a Dictionary is UPDATED.\n */\n public onDictionaryChange?: (dictionary: DictionaryAPI) => any;\n\n /**\n * Callback triggered when a Dictionary is DELETED.\n */\n public onDictionaryDeleted?: (dictionary: DictionaryAPI) => any;\n\n /**\n * Callback triggered when connection is established or re-established.\n */\n public onConnectionOpen?: () => any;\n\n /**\n * Callback triggered when connection encounters an error.\n */\n public onConnectionError?: (error: Event) => any;\n\n constructor(\n private intlayerConfig: Pick<IntlayerConfig, 'log' | 'editor'> = {\n log,\n editor,\n }\n ) {\n this.appLogger = getAppLogger(this.intlayerConfig);\n }\n\n /**\n * Initializes the EventSource connection using the given intlayerConfig\n * (or the default config if none was provided).\n */\n public async initialize(): Promise<void> {\n this.isManuallyDisconnected = false;\n await this.connect();\n }\n\n /**\n * Establishes the EventSource connection with automatic reconnection support.\n */\n private async connect(): Promise<void> {\n try {\n const backendURL = this.intlayerConfig.editor.backendURL;\n\n // Retrieve the access token via proxy\n const accessToken = await getIntlayerAPIProxy(\n undefined,\n this.intlayerConfig\n ).oAuth.getOAuth2AccessToken();\n\n if (!accessToken) {\n throw new Error('Failed to retrieve access token');\n }\n\n const API_ROUTE = `${backendURL}/api/event-listener`;\n\n // Close existing connection if any\n if (this.eventSource) {\n this.eventSource.close();\n }\n\n this.eventSource = new EventSource(API_ROUTE, {\n fetch: (input, init) =>\n fetch(input, {\n ...init,\n headers: {\n ...(init?.headers ?? {}),\n Authorization: `Bearer ${accessToken.data?.accessToken}`,\n },\n }),\n });\n\n this.eventSource.onopen = () => {\n this.reconnectAttempts = 0;\n this.reconnectDelay = 1000; // Reset delay\n this.onConnectionOpen?.();\n };\n\n this.eventSource.onmessage = (event) => this.handleMessage(event);\n this.eventSource.onerror = (event) => this.handleError(event);\n } catch (_error) {\n this.appLogger('Failed to establish EventSource connection:', {\n level: 'error',\n });\n this.scheduleReconnect();\n }\n }\n\n /**\n * Cleans up (closes) the EventSource connection.\n */\n public cleanup(): void {\n this.isManuallyDisconnected = true;\n\n if (this.reconnectTimeout) {\n clearTimeout(this.reconnectTimeout);\n this.reconnectTimeout = null;\n }\n\n if (this.eventSource) {\n this.eventSource.close();\n this.eventSource = null;\n }\n }\n\n /**\n * Schedules a reconnection attempt with exponential backoff.\n */\n private scheduleReconnect(): void {\n if (\n this.isManuallyDisconnected ||\n this.reconnectAttempts >= this.maxReconnectAttempts\n ) {\n if (this.reconnectAttempts >= this.maxReconnectAttempts) {\n this.appLogger(\n [\n `Max reconnection attempts (${this.maxReconnectAttempts}) reached. Giving up.`,\n ],\n {\n level: 'error',\n }\n );\n }\n return;\n }\n\n this.reconnectAttempts++;\n const delay = this.reconnectDelay * 2 ** (this.reconnectAttempts - 1); // Exponential backoff\n\n this.appLogger(\n `Scheduling reconnection attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts} in ${delay}ms`\n );\n\n this.reconnectTimeout = setTimeout(async () => {\n if (!this.isManuallyDisconnected) {\n await this.connect();\n }\n }, delay);\n }\n\n /**\n * Handles incoming SSE messages, parses the event data,\n * and invokes the appropriate callback.\n */\n private async handleMessage(event: IntlayerMessageEvent): Promise<void> {\n try {\n const { data } = event;\n\n const dataJSON: MessageEventData[] = JSON.parse(data);\n\n for (const dataEl of dataJSON) {\n switch (dataEl.object) {\n case 'DICTIONARY':\n switch (dataEl.status) {\n case 'ADDED':\n await this.onDictionaryAdded?.(dataEl.data);\n break;\n case 'UPDATED':\n await this.onDictionaryChange?.(dataEl.data);\n break;\n case 'DELETED':\n await this.onDictionaryDeleted?.(dataEl.data);\n break;\n default:\n this.appLogger(\n ['Unhandled dictionary status:', dataEl.status],\n {\n level: 'error',\n }\n );\n break;\n }\n break;\n default:\n this.appLogger(['Unknown object type:', dataEl.object], {\n level: 'error',\n });\n break;\n }\n }\n } catch (error) {\n this.appLogger(['Error processing dictionary update:', error], {\n level: 'error',\n });\n }\n }\n\n /**\n * Handles any SSE errors and attempts reconnection if appropriate.\n */\n private handleError(event: Event): void {\n const errorEvent = event as any;\n\n // Log detailed error information\n this.appLogger(\n [\n 'EventSource error:',\n {\n type: errorEvent.type,\n message: errorEvent.message,\n code: errorEvent.code,\n readyState: this.eventSource?.readyState,\n url: this.eventSource?.url,\n },\n ],\n {\n level: 'error',\n }\n );\n\n // Notify error callback\n this.onConnectionError?.(event);\n\n // Check if this is a connection close error\n const isConnectionClosed =\n errorEvent.type === 'error' &&\n (errorEvent.message?.includes('terminated') ||\n errorEvent.message?.includes('closed') ||\n this.eventSource?.readyState === EventSource.CLOSED);\n\n if (isConnectionClosed && !this.isManuallyDisconnected) {\n this.appLogger(\n 'Connection was terminated by server, attempting to reconnect...'\n );\n this.scheduleReconnect();\n } else {\n // For other types of errors, close the connection\n this.cleanup();\n }\n }\n}\n"],"mappings":"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"}
|
package/dist/cjs/auth/login.cjs
CHANGED
|
@@ -164,6 +164,17 @@ const login = async (options = {}) => {
|
|
|
164
164
|
res.end("Not found");
|
|
165
165
|
}
|
|
166
166
|
});
|
|
167
|
+
const abortLogin = () => {
|
|
168
|
+
server.close();
|
|
169
|
+
server.closeAllConnections();
|
|
170
|
+
resolve();
|
|
171
|
+
};
|
|
172
|
+
if (options.signal?.aborted) {
|
|
173
|
+
resolve();
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
options.signal?.addEventListener("abort", abortLogin, { once: true });
|
|
177
|
+
server.on("close", () => options.signal?.removeEventListener("abort", abortLogin));
|
|
167
178
|
server.listen(0, () => {
|
|
168
179
|
const address = server.address();
|
|
169
180
|
const port = typeof address === "object" && address ? address.port : 0;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"login.cjs","names":["getConfiguration","getAppLogger","http","URL","colorize","ANSIColors","writeCliSessionToken","colorizePath"],"sources":["../../../src/auth/login.ts"],"sourcesContent":["import http from 'node:http';\nimport { URL } from 'node:url';\nimport * as ANSIColors from '@intlayer/config/colors';\nimport { colorize, colorizePath, getAppLogger } from '@intlayer/config/logger';\nimport {\n type GetConfigurationOptions,\n getConfiguration,\n} from '@intlayer/config/node';\nimport { logConfigDetails } from '@intlayer/engine/cli';\nimport { openBrowser } from '../utils/openBrowser';\nimport { writeCliSessionToken } from './sessionToken';\n\nconst buildSuccessHtml = (message: string): string => `\n <!DOCTYPE html>\n <html lang=\"en\" data-theme=\"dark\">\n <head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>Intlayer CLI Login</title>\n <style>\n :root {\n --color-background: rgba(23, 23, 23);\n --color-card: rgba(39, 39, 39);\n --color-text: rgba(255, 245, 237);\n --color-neutral: rgba(93, 93, 93);\n --font-sans: \"Inter\", -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;\n }\n * { box-sizing: border-box; }\n body {\n font-family: var(--font-sans);\n display: flex;\n align-items: center;\n justify-content: center;\n min-height: 100vh;\n margin: 0;\n padding: 1rem;\n background-color: var(--color-background);\n color: var(--color-text);\n }\n .container {\n text-align: center;\n padding: 2rem;\n border-radius: 1rem;\n background-color: var(--color-card);\n box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);\n max-width: 400px;\n width: 100%;\n }\n h1 { margin: 0 0 1rem 0; font-size: 1.5rem; font-weight: 700; color: var(--color-text); }\n p { color: var(--color-neutral); font-size: 0.8rem; margin: 0 0 1.5rem 0; line-height: 1.5; }\n </style>\n </head>\n <body>\n <div class=\"container\">\n <h1>Login Successful</h1>\n <p>${message}</p>\n </div>\n <script>\n window.close();\n setTimeout(() => { window.close(); }, 1000);\n </script>\n </body>\n </html>\n`;\n\ntype LoginOptions = {\n cmsUrl?: string;\n configOptions?: GetConfigurationOptions;\n /**\n * When false, do not call process.exit(0) after a successful login so the\n * caller can continue (e.g. retry a command inline, or finish an interactive\n * setup). Defaults to true to preserve existing standalone-login behaviour.\n */\n exitAfter?: boolean;\n /**\n * Invoked with the access-key credentials when the login flow returns a\n * `clientId` / `clientSecret` pair. When provided, the caller takes ownership\n * of persisting the credentials (e.g. writing them to `.env` and enabling the\n * editor in the config), so the default manual-setup instructions are skipped.\n */\n onCredentials?: (credentials: {\n clientId: string;\n clientSecret: string;\n }) => void | Promise<void>;\n};\n\nexport const login = async (options: LoginOptions = {}) => {\n const configuration = getConfiguration(options.configOptions);\n logConfigDetails(options?.configOptions);\n\n const logger = getAppLogger(configuration);\n\n const cmsUrl = options.cmsUrl ?? configuration.editor.cmsURL;\n\n return new Promise<void>((resolve) => {\n /**\n * Sends the success page, then shuts the callback server down. Open\n * keep-alive sockets are dropped: `server.close` alone waits for the\n * browser to release them, which would leave the login pending forever.\n */\n const completeLogin = (\n response: http.ServerResponse,\n message: string\n ): void => {\n response.writeHead(200, {\n 'Content-Type': 'text/html',\n Connection: 'close',\n });\n response.end(buildSuccessHtml(message), () => {\n server.close();\n server.closeAllConnections();\n resolve();\n\n if (options.exitAfter !== false) {\n process.exit(0);\n }\n });\n };\n\n const server = http.createServer(async (req, res) => {\n const url = new URL(req.url ?? '', `http://${req.headers.host}`);\n\n // Set CORS headers\n res.setHeader('Access-Control-Allow-Origin', '*');\n res.setHeader('Access-Control-Allow-Methods', 'GET, OPTIONS');\n res.setHeader('Access-Control-Allow-Headers', 'Content-Type');\n\n if (req.method === 'OPTIONS') {\n res.writeHead(204);\n res.end();\n return;\n }\n\n if (url.pathname === '/callback') {\n const sessionToken = url.searchParams.get('sessionToken');\n const sessionExpiresAt = url.searchParams.get('expiresAt');\n const clientId = url.searchParams.get('clientId');\n const clientSecret = url.searchParams.get('clientSecret');\n\n if (sessionToken && sessionExpiresAt) {\n logger('');\n logger(\n `Log in successful. ${colorize('2h', ANSIColors.BLUE)} session token received.`\n );\n logger('');\n\n logger(\n colorize(\n `Token expires at: ${new Date(sessionExpiresAt).toLocaleString()}`,\n ANSIColors.GREY\n )\n );\n\n await writeCliSessionToken(\n configuration,\n sessionToken,\n new Date(sessionExpiresAt)\n );\n\n if (options.onCredentials) {\n logger(\n 'A session token does not configure the CMS: run `intlayer login` again and pick an access key to store the credentials in your .env.',\n { level: 'warn' }\n );\n }\n\n completeLogin(\n res,\n 'Your 2h session token has been stored. You can now close this tab and return to your terminal.'\n );\n return;\n }\n\n if (clientId && clientSecret) {\n if (options.onCredentials) {\n // The caller persists the credentials itself (e.g. interactive\n // init writing `.env` and enabling the editor in the config).\n logger('');\n logger('Log in successful. Configuring Intlayer CMS...');\n\n try {\n await options.onCredentials({ clientId, clientSecret });\n } catch (error) {\n logger(\n `Failed to persist the CMS credentials automatically: ${\n error instanceof Error ? error.message : String(error)\n }`,\n { level: 'error' }\n );\n }\n } else {\n logger('');\n logger('Log in successful. Client ID and Client Secret received.');\n\n logger('');\n logger([\n '1. Insert the Client ID and Client Secret in your',\n colorizePath('.env'),\n 'file:',\n ]);\n logger(\n colorize('--------------------------------', ANSIColors.GREY_DARK)\n );\n logger(\n [\n colorize('INTLAYER_CLIENT_ID=', ANSIColors.GREY_LIGHT),\n colorize(clientId, ANSIColors.BLUE),\n ].join('')\n );\n logger(\n [\n colorize('INTLAYER_CLIENT_SECRET=', ANSIColors.GREY_LIGHT),\n colorize(clientSecret, ANSIColors.BLUE),\n ].join('')\n );\n logger(\n colorize('--------------------------------', ANSIColors.GREY_DARK)\n );\n logger('');\n logger('2. Insert in your Intlayer configuration file:');\n logger(\n colorize('--------------------------------', ANSIColors.GREY_DARK)\n );\n [\n `${ANSIColors.GREY_LIGHT}{`,\n ` editor: {`,\n ` enabled: true,`,\n ` cmsURL: '${colorizePath(cmsUrl!, undefined, ANSIColors.GREY_LIGHT)}',`,\n ` clientId: '${colorize('process.env.INTLAYER_CLIENT_ID', ANSIColors.BLUE, ANSIColors.GREY_LIGHT)}',`,\n ` clientSecret: '${colorize('process.env.INTLAYER_CLIENT_SECRET', ANSIColors.BLUE, ANSIColors.GREY_LIGHT)}',`,\n ` },`,\n `}`,\n ].forEach((line) => {\n logger(line);\n });\n logger(\n colorize('--------------------------------', ANSIColors.GREY_DARK)\n );\n }\n\n completeLogin(\n res,\n 'You have successfully logged in to Intlayer CLI. You can now close this tab and return to your terminal.'\n );\n } else {\n res.writeHead(400, { 'Content-Type': 'text/plain' });\n res.end('Missing parameters');\n }\n } else {\n res.writeHead(404, { 'Content-Type': 'text/plain' });\n res.end('Not found');\n }\n });\n\n server.listen(0, () => {\n const address = server.address();\n const port = typeof address === 'object' && address ? address.port : 0;\n const state = Math.random().toString(36).substring(7);\n\n const websiteUrl =\n cmsUrl ?? process.env.INTLAYER_SITE_URL ?? 'http://localhost:3000';\n const backendUrl = configuration.editor.backendURL ?? '';\n const loginUrl = `${websiteUrl}/auth/cli-login?port=${port}&state=${state}&backendUrl=${encodeURIComponent(backendUrl)}`;\n\n logger('Opening browser for login...');\n logger(`If browser does not open, visit: ${colorizePath(loginUrl)}`);\n\n openBrowser(loginUrl);\n });\n });\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"login.cjs","names":["getConfiguration","getAppLogger","http","URL","colorize","ANSIColors","writeCliSessionToken","colorizePath"],"sources":["../../../src/auth/login.ts"],"sourcesContent":["import http from 'node:http';\nimport { URL } from 'node:url';\nimport * as ANSIColors from '@intlayer/config/colors';\nimport { colorize, colorizePath, getAppLogger } from '@intlayer/config/logger';\nimport {\n type GetConfigurationOptions,\n getConfiguration,\n} from '@intlayer/config/node';\nimport { logConfigDetails } from '@intlayer/engine/cli';\nimport { openBrowser } from '../utils/openBrowser';\nimport { writeCliSessionToken } from './sessionToken';\n\nconst buildSuccessHtml = (message: string): string => `\n <!DOCTYPE html>\n <html lang=\"en\" data-theme=\"dark\">\n <head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>Intlayer CLI Login</title>\n <style>\n :root {\n --color-background: rgba(23, 23, 23);\n --color-card: rgba(39, 39, 39);\n --color-text: rgba(255, 245, 237);\n --color-neutral: rgba(93, 93, 93);\n --font-sans: \"Inter\", -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;\n }\n * { box-sizing: border-box; }\n body {\n font-family: var(--font-sans);\n display: flex;\n align-items: center;\n justify-content: center;\n min-height: 100vh;\n margin: 0;\n padding: 1rem;\n background-color: var(--color-background);\n color: var(--color-text);\n }\n .container {\n text-align: center;\n padding: 2rem;\n border-radius: 1rem;\n background-color: var(--color-card);\n box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);\n max-width: 400px;\n width: 100%;\n }\n h1 { margin: 0 0 1rem 0; font-size: 1.5rem; font-weight: 700; color: var(--color-text); }\n p { color: var(--color-neutral); font-size: 0.8rem; margin: 0 0 1.5rem 0; line-height: 1.5; }\n </style>\n </head>\n <body>\n <div class=\"container\">\n <h1>Login Successful</h1>\n <p>${message}</p>\n </div>\n <script>\n window.close();\n setTimeout(() => { window.close(); }, 1000);\n </script>\n </body>\n </html>\n`;\n\ntype LoginOptions = {\n cmsUrl?: string;\n configOptions?: GetConfigurationOptions;\n /**\n * When false, do not call process.exit(0) after a successful login so the\n * caller can continue (e.g. retry a command inline, or finish an interactive\n * setup). Defaults to true to preserve existing standalone-login behaviour.\n */\n exitAfter?: boolean;\n /**\n * Invoked with the access-key credentials when the login flow returns a\n * `clientId` / `clientSecret` pair. When provided, the caller takes ownership\n * of persisting the credentials (e.g. writing them to `.env` and enabling the\n * editor in the config), so the default manual-setup instructions are skipped.\n */\n onCredentials?: (credentials: {\n clientId: string;\n clientSecret: string;\n }) => void | Promise<void>;\n /**\n * Aborts a login still waiting for the browser callback: the callback\n * server is shut down and the returned promise resolves.\n */\n signal?: AbortSignal;\n};\n\nexport const login = async (options: LoginOptions = {}) => {\n const configuration = getConfiguration(options.configOptions);\n logConfigDetails(options?.configOptions);\n\n const logger = getAppLogger(configuration);\n\n const cmsUrl = options.cmsUrl ?? configuration.editor.cmsURL;\n\n return new Promise<void>((resolve) => {\n /**\n * Sends the success page, then shuts the callback server down. Open\n * keep-alive sockets are dropped: `server.close` alone waits for the\n * browser to release them, which would leave the login pending forever.\n */\n const completeLogin = (\n response: http.ServerResponse,\n message: string\n ): void => {\n response.writeHead(200, {\n 'Content-Type': 'text/html',\n Connection: 'close',\n });\n response.end(buildSuccessHtml(message), () => {\n server.close();\n server.closeAllConnections();\n resolve();\n\n if (options.exitAfter !== false) {\n process.exit(0);\n }\n });\n };\n\n const server = http.createServer(async (req, res) => {\n const url = new URL(req.url ?? '', `http://${req.headers.host}`);\n\n // Set CORS headers\n res.setHeader('Access-Control-Allow-Origin', '*');\n res.setHeader('Access-Control-Allow-Methods', 'GET, OPTIONS');\n res.setHeader('Access-Control-Allow-Headers', 'Content-Type');\n\n if (req.method === 'OPTIONS') {\n res.writeHead(204);\n res.end();\n return;\n }\n\n if (url.pathname === '/callback') {\n const sessionToken = url.searchParams.get('sessionToken');\n const sessionExpiresAt = url.searchParams.get('expiresAt');\n const clientId = url.searchParams.get('clientId');\n const clientSecret = url.searchParams.get('clientSecret');\n\n if (sessionToken && sessionExpiresAt) {\n logger('');\n logger(\n `Log in successful. ${colorize('2h', ANSIColors.BLUE)} session token received.`\n );\n logger('');\n\n logger(\n colorize(\n `Token expires at: ${new Date(sessionExpiresAt).toLocaleString()}`,\n ANSIColors.GREY\n )\n );\n\n await writeCliSessionToken(\n configuration,\n sessionToken,\n new Date(sessionExpiresAt)\n );\n\n if (options.onCredentials) {\n logger(\n 'A session token does not configure the CMS: run `intlayer login` again and pick an access key to store the credentials in your .env.',\n { level: 'warn' }\n );\n }\n\n completeLogin(\n res,\n 'Your 2h session token has been stored. You can now close this tab and return to your terminal.'\n );\n return;\n }\n\n if (clientId && clientSecret) {\n if (options.onCredentials) {\n // The caller persists the credentials itself (e.g. interactive\n // init writing `.env` and enabling the editor in the config).\n logger('');\n logger('Log in successful. Configuring Intlayer CMS...');\n\n try {\n await options.onCredentials({ clientId, clientSecret });\n } catch (error) {\n logger(\n `Failed to persist the CMS credentials automatically: ${\n error instanceof Error ? error.message : String(error)\n }`,\n { level: 'error' }\n );\n }\n } else {\n logger('');\n logger('Log in successful. Client ID and Client Secret received.');\n\n logger('');\n logger([\n '1. Insert the Client ID and Client Secret in your',\n colorizePath('.env'),\n 'file:',\n ]);\n logger(\n colorize('--------------------------------', ANSIColors.GREY_DARK)\n );\n logger(\n [\n colorize('INTLAYER_CLIENT_ID=', ANSIColors.GREY_LIGHT),\n colorize(clientId, ANSIColors.BLUE),\n ].join('')\n );\n logger(\n [\n colorize('INTLAYER_CLIENT_SECRET=', ANSIColors.GREY_LIGHT),\n colorize(clientSecret, ANSIColors.BLUE),\n ].join('')\n );\n logger(\n colorize('--------------------------------', ANSIColors.GREY_DARK)\n );\n logger('');\n logger('2. Insert in your Intlayer configuration file:');\n logger(\n colorize('--------------------------------', ANSIColors.GREY_DARK)\n );\n [\n `${ANSIColors.GREY_LIGHT}{`,\n ` editor: {`,\n ` enabled: true,`,\n ` cmsURL: '${colorizePath(cmsUrl!, undefined, ANSIColors.GREY_LIGHT)}',`,\n ` clientId: '${colorize('process.env.INTLAYER_CLIENT_ID', ANSIColors.BLUE, ANSIColors.GREY_LIGHT)}',`,\n ` clientSecret: '${colorize('process.env.INTLAYER_CLIENT_SECRET', ANSIColors.BLUE, ANSIColors.GREY_LIGHT)}',`,\n ` },`,\n `}`,\n ].forEach((line) => {\n logger(line);\n });\n logger(\n colorize('--------------------------------', ANSIColors.GREY_DARK)\n );\n }\n\n completeLogin(\n res,\n 'You have successfully logged in to Intlayer CLI. You can now close this tab and return to your terminal.'\n );\n } else {\n res.writeHead(400, { 'Content-Type': 'text/plain' });\n res.end('Missing parameters');\n }\n } else {\n res.writeHead(404, { 'Content-Type': 'text/plain' });\n res.end('Not found');\n }\n });\n\n const abortLogin = (): void => {\n server.close();\n server.closeAllConnections();\n resolve();\n };\n\n if (options.signal?.aborted) {\n resolve();\n return;\n }\n\n options.signal?.addEventListener('abort', abortLogin, { once: true });\n server.on('close', () =>\n options.signal?.removeEventListener('abort', abortLogin)\n );\n\n server.listen(0, () => {\n const address = server.address();\n const port = typeof address === 'object' && address ? address.port : 0;\n const state = Math.random().toString(36).substring(7);\n\n const websiteUrl =\n cmsUrl ?? process.env.INTLAYER_SITE_URL ?? 'http://localhost:3000';\n const backendUrl = configuration.editor.backendURL ?? '';\n const loginUrl = `${websiteUrl}/auth/cli-login?port=${port}&state=${state}&backendUrl=${encodeURIComponent(backendUrl)}`;\n\n logger('Opening browser for login...');\n logger(`If browser does not open, visit: ${colorizePath(loginUrl)}`);\n\n openBrowser(loginUrl);\n });\n });\n};\n"],"mappings":"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"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
|
+
const require_runtime = require('../_virtual/_rolldown/runtime.cjs');
|
|
3
|
+
const require_utils_openBrowser = require('../utils/openBrowser.cjs');
|
|
4
|
+
let node_http = require("node:http");
|
|
5
|
+
node_http = require_runtime.__toESM(node_http);
|
|
6
|
+
let node_url = require("node:url");
|
|
7
|
+
let _intlayer_config_colors = require("@intlayer/config/colors");
|
|
8
|
+
_intlayer_config_colors = require_runtime.__toESM(_intlayer_config_colors);
|
|
9
|
+
let _intlayer_config_logger = require("@intlayer/config/logger");
|
|
10
|
+
let _intlayer_config_node = require("@intlayer/config/node");
|
|
11
|
+
let node_crypto = require("node:crypto");
|
|
12
|
+
|
|
13
|
+
//#region src/auth/setupRepository.ts
|
|
14
|
+
/** Page shown once the browser handed the result back to the CLI. */
|
|
15
|
+
const DONE_HTML = `<!DOCTYPE html>
|
|
16
|
+
<html lang="en">
|
|
17
|
+
<head>
|
|
18
|
+
<meta charset="UTF-8">
|
|
19
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
20
|
+
<title>Intlayer CLI</title>
|
|
21
|
+
<style>
|
|
22
|
+
body { font-family: system-ui, sans-serif; display: flex; align-items: center; justify-content: center; min-height: 100vh; margin: 0; background: rgb(23, 23, 23); color: rgb(255, 245, 237); }
|
|
23
|
+
p { color: rgb(160, 160, 160); font-size: 0.9rem; }
|
|
24
|
+
</style>
|
|
25
|
+
</head>
|
|
26
|
+
<body>
|
|
27
|
+
<div>
|
|
28
|
+
<h1>Repository setup complete</h1>
|
|
29
|
+
<p>You can now close this tab and return to your terminal.</p>
|
|
30
|
+
</div>
|
|
31
|
+
<script>window.close();<\/script>
|
|
32
|
+
</body>
|
|
33
|
+
</html>`;
|
|
34
|
+
/**
|
|
35
|
+
* Search params pre-filling the repository page with the local repository.
|
|
36
|
+
* Empty when none was detected: the user then picks it in the browser.
|
|
37
|
+
*/
|
|
38
|
+
const getRepositorySearchParams = (detectedRepository) => {
|
|
39
|
+
const searchParams = new URLSearchParams();
|
|
40
|
+
if (!detectedRepository) return searchParams;
|
|
41
|
+
const { provider, owner, repository, branch, configFilePath, instanceUrl } = detectedRepository;
|
|
42
|
+
searchParams.set("repositoryProvider", provider);
|
|
43
|
+
searchParams.set("repositoryOwner", owner);
|
|
44
|
+
searchParams.set("repositoryName", repository);
|
|
45
|
+
if (branch) searchParams.set("repositoryBranch", branch);
|
|
46
|
+
if (configFilePath) searchParams.set("repositoryConfigPath", configFilePath);
|
|
47
|
+
if (instanceUrl) searchParams.set("repositoryInstanceUrl", instanceUrl);
|
|
48
|
+
return searchParams;
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* Opens the CMS repository page (`/auth/cli-repository`) and waits for it to
|
|
52
|
+
* call back the local server once the repository and build settings are set.
|
|
53
|
+
*/
|
|
54
|
+
const setupRepository = async (options = {}) => {
|
|
55
|
+
const configuration = (0, _intlayer_config_node.getConfiguration)(options.configOptions);
|
|
56
|
+
const logger = (0, _intlayer_config_logger.getAppLogger)(configuration);
|
|
57
|
+
const cmsUrl = options.cmsUrl ?? configuration.editor.cmsURL ?? process.env.INTLAYER_SITE_URL ?? "http://localhost:3000";
|
|
58
|
+
const state = (0, node_crypto.randomBytes)(16).toString("hex");
|
|
59
|
+
return new Promise((resolve) => {
|
|
60
|
+
const server = node_http.default.createServer((request, response) => {
|
|
61
|
+
const url = new node_url.URL(request.url ?? "", `http://${request.headers.host}`);
|
|
62
|
+
if (url.pathname !== "/callback" || url.searchParams.get("state") !== state) {
|
|
63
|
+
response.writeHead(404, { "Content-Type": "text/plain" });
|
|
64
|
+
response.end("Not found");
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
const linkedRepository = url.searchParams.get("linkedRepository");
|
|
68
|
+
logger(linkedRepository ? `Repository ${(0, _intlayer_config_logger.colorize)(linkedRepository, _intlayer_config_colors.BLUE)} connected to the CMS project.` : "Repository setup skipped.");
|
|
69
|
+
response.writeHead(200, {
|
|
70
|
+
"Content-Type": "text/html",
|
|
71
|
+
Connection: "close"
|
|
72
|
+
});
|
|
73
|
+
response.end(DONE_HTML, () => {
|
|
74
|
+
server.close();
|
|
75
|
+
server.closeAllConnections();
|
|
76
|
+
resolve({ linkedRepository });
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
server.listen(0, () => {
|
|
80
|
+
const address = server.address();
|
|
81
|
+
const port = typeof address === "object" && address ? address.port : 0;
|
|
82
|
+
const searchParams = getRepositorySearchParams(options.detectedRepository);
|
|
83
|
+
searchParams.set("port", String(port));
|
|
84
|
+
searchParams.set("state", state);
|
|
85
|
+
const pageUrl = `${cmsUrl}/auth/cli-repository?${searchParams.toString()}`;
|
|
86
|
+
logger(`If the browser does not open, visit: ${(0, _intlayer_config_logger.colorizePath)(pageUrl)}`);
|
|
87
|
+
require_utils_openBrowser.openBrowser(pageUrl);
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
//#endregion
|
|
93
|
+
exports.getRepositorySearchParams = getRepositorySearchParams;
|
|
94
|
+
exports.setupRepository = setupRepository;
|
|
95
|
+
//# sourceMappingURL=setupRepository.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"setupRepository.cjs","names":["getConfiguration","getAppLogger","randomBytes","http","URL","colorize","ANSIColors","colorizePath"],"sources":["../../../src/auth/setupRepository.ts"],"sourcesContent":["import { randomBytes } from 'node:crypto';\nimport http from 'node:http';\nimport { URL } from 'node:url';\nimport * as ANSIColors from '@intlayer/config/colors';\nimport { colorize, colorizePath, getAppLogger } from '@intlayer/config/logger';\nimport {\n type GetConfigurationOptions,\n getConfiguration,\n} from '@intlayer/config/node';\nimport type { DetectedGitRepository } from '../utils/detectGitRepository';\nimport { openBrowser } from '../utils/openBrowser';\n\n/** Page shown once the browser handed the result back to the CLI. */\nconst DONE_HTML = `<!DOCTYPE html>\n<html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>Intlayer CLI</title>\n <style>\n body { font-family: system-ui, sans-serif; display: flex; align-items: center; justify-content: center; min-height: 100vh; margin: 0; background: rgb(23, 23, 23); color: rgb(255, 245, 237); }\n p { color: rgb(160, 160, 160); font-size: 0.9rem; }\n </style>\n </head>\n <body>\n <div>\n <h1>Repository setup complete</h1>\n <p>You can now close this tab and return to your terminal.</p>\n </div>\n <script>window.close();</script>\n </body>\n</html>`;\n\n/**\n * Search params pre-filling the repository page with the local repository.\n * Empty when none was detected: the user then picks it in the browser.\n */\nexport const getRepositorySearchParams = (\n detectedRepository: DetectedGitRepository | null | undefined\n): URLSearchParams => {\n const searchParams = new URLSearchParams();\n\n if (!detectedRepository) return searchParams;\n\n const { provider, owner, repository, branch, configFilePath, instanceUrl } =\n detectedRepository;\n\n searchParams.set('repositoryProvider', provider);\n searchParams.set('repositoryOwner', owner);\n searchParams.set('repositoryName', repository);\n if (branch) searchParams.set('repositoryBranch', branch);\n if (configFilePath) searchParams.set('repositoryConfigPath', configFilePath);\n if (instanceUrl) searchParams.set('repositoryInstanceUrl', instanceUrl);\n\n return searchParams;\n};\n\ntype SetupRepositoryOptions = {\n cmsUrl?: string;\n configOptions?: GetConfigurationOptions;\n /** Local repository pre-filling the page */\n detectedRepository?: DetectedGitRepository | null;\n};\n\n/** Outcome of the browser step: `provider:owner/repository`, or skipped. */\nexport type SetupRepositoryResult = {\n linkedRepository: string | null;\n};\n\n/**\n * Opens the CMS repository page (`/auth/cli-repository`) and waits for it to\n * call back the local server once the repository and build settings are set.\n */\nexport const setupRepository = async (\n options: SetupRepositoryOptions = {}\n): Promise<SetupRepositoryResult> => {\n const configuration = getConfiguration(options.configOptions);\n const logger = getAppLogger(configuration);\n const cmsUrl =\n options.cmsUrl ??\n configuration.editor.cmsURL ??\n process.env.INTLAYER_SITE_URL ??\n 'http://localhost:3000';\n const state = randomBytes(16).toString('hex');\n\n return new Promise<SetupRepositoryResult>((resolve) => {\n const server = http.createServer((request, response) => {\n const url = new URL(request.url ?? '', `http://${request.headers.host}`);\n\n // A callback from another tab or process is ignored\n if (\n url.pathname !== '/callback' ||\n url.searchParams.get('state') !== state\n ) {\n response.writeHead(404, { 'Content-Type': 'text/plain' });\n response.end('Not found');\n return;\n }\n\n const linkedRepository = url.searchParams.get('linkedRepository');\n\n logger(\n linkedRepository\n ? `Repository ${colorize(linkedRepository, ANSIColors.BLUE)} connected to the CMS project.`\n : 'Repository setup skipped.'\n );\n\n response.writeHead(200, {\n 'Content-Type': 'text/html',\n Connection: 'close',\n });\n response.end(DONE_HTML, () => {\n // Keep-alive sockets would otherwise hold the server open\n server.close();\n server.closeAllConnections();\n resolve({ linkedRepository });\n });\n });\n\n server.listen(0, () => {\n const address = server.address();\n const port = typeof address === 'object' && address ? address.port : 0;\n const searchParams = getRepositorySearchParams(\n options.detectedRepository\n );\n searchParams.set('port', String(port));\n searchParams.set('state', state);\n\n const pageUrl = `${cmsUrl}/auth/cli-repository?${searchParams.toString()}`;\n\n logger(`If the browser does not open, visit: ${colorizePath(pageUrl)}`);\n\n openBrowser(pageUrl);\n });\n });\n};\n"],"mappings":";;;;;;;;;;;;;;AAaA,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;AAwBlB,MAAa,6BACX,uBACoB;CACpB,MAAM,eAAe,IAAI,gBAAgB;CAEzC,IAAI,CAAC,oBAAoB,OAAO;CAEhC,MAAM,EAAE,UAAU,OAAO,YAAY,QAAQ,gBAAgB,gBAC3D;CAEF,aAAa,IAAI,sBAAsB,QAAQ;CAC/C,aAAa,IAAI,mBAAmB,KAAK;CACzC,aAAa,IAAI,kBAAkB,UAAU;CAC7C,IAAI,QAAQ,aAAa,IAAI,oBAAoB,MAAM;CACvD,IAAI,gBAAgB,aAAa,IAAI,wBAAwB,cAAc;CAC3E,IAAI,aAAa,aAAa,IAAI,yBAAyB,WAAW;CAEtE,OAAO;AACT;;;;;AAkBA,MAAa,kBAAkB,OAC7B,UAAkC,CAAC,MACA;CACnC,MAAM,oBAAgBA,wCAAiB,QAAQ,aAAa;CAC5D,MAAM,aAASC,sCAAa,aAAa;CACzC,MAAM,SACJ,QAAQ,UACR,cAAc,OAAO,UACrB,QAAQ,IAAI,qBACZ;CACF,MAAM,YAAQC,yBAAY,EAAE,CAAC,CAAC,SAAS,KAAK;CAE5C,OAAO,IAAI,SAAgC,YAAY;EACrD,MAAM,SAASC,kBAAK,cAAc,SAAS,aAAa;GACtD,MAAM,MAAM,IAAIC,aAAI,QAAQ,OAAO,IAAI,UAAU,QAAQ,QAAQ,MAAM;GAGvE,IACE,IAAI,aAAa,eACjB,IAAI,aAAa,IAAI,OAAO,MAAM,OAClC;IACA,SAAS,UAAU,KAAK,EAAE,gBAAgB,aAAa,CAAC;IACxD,SAAS,IAAI,WAAW;IACxB;GACF;GAEA,MAAM,mBAAmB,IAAI,aAAa,IAAI,kBAAkB;GAEhE,OACE,mBACI,kBAAcC,kCAAS,kBAAkBC,wBAAW,IAAI,EAAE,kCAC1D,2BACN;GAEA,SAAS,UAAU,KAAK;IACtB,gBAAgB;IAChB,YAAY;GACd,CAAC;GACD,SAAS,IAAI,iBAAiB;IAE5B,OAAO,MAAM;IACb,OAAO,oBAAoB;IAC3B,QAAQ,EAAE,iBAAiB,CAAC;GAC9B,CAAC;EACH,CAAC;EAED,OAAO,OAAO,SAAS;GACrB,MAAM,UAAU,OAAO,QAAQ;GAC/B,MAAM,OAAO,OAAO,YAAY,YAAY,UAAU,QAAQ,OAAO;GACrE,MAAM,eAAe,0BACnB,QAAQ,kBACV;GACA,aAAa,IAAI,QAAQ,OAAO,IAAI,CAAC;GACrC,aAAa,IAAI,SAAS,KAAK;GAE/B,MAAM,UAAU,GAAG,OAAO,uBAAuB,aAAa,SAAS;GAEvE,OAAO,4CAAwCC,sCAAa,OAAO,GAAG;GAEtE,sCAAY,OAAO;EACrB,CAAC;CACH,CAAC;AACH"}
|
package/dist/cjs/ci.cjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
|
-
let node_path = require("node:path");
|
|
3
2
|
let _intlayer_engine_cli = require("@intlayer/engine/cli");
|
|
3
|
+
let node_path = require("node:path");
|
|
4
4
|
let _intlayer_config_logger = require("@intlayer/config/logger");
|
|
5
5
|
let node_child_process = require("node:child_process");
|
|
6
6
|
|
package/dist/cjs/cli.cjs
CHANGED
|
@@ -154,31 +154,75 @@ const setAPI = () => {
|
|
|
154
154
|
/**
|
|
155
155
|
* INIT
|
|
156
156
|
*/
|
|
157
|
-
const initCmd = program.command("init").description("
|
|
158
|
-
const { init, parseLocaleRoutingChoice, getRoutingInitOptions } = await Promise.resolve().then(() => require("./init.cjs"));
|
|
157
|
+
const initCmd = program.command("init").enablePositionalOptions().description("Install the Intlayer packages and set up the framework. Use --interactive, or the init subcommands, for the other setup steps (CI, skills, MCP, VS Code, LSP, lint, CMS, …)").option("--project-root [projectRoot]", "Project root directory").option("--no-gitignore", "Do not add .intlayer to .gitignore").option("--no-github-actions", "With --interactive: do not scaffold the GitHub Actions workflows, even if selected").option("--no-framework-setup", "Do not set up the project (Intlayer config, tsconfig, bundler plugin, middleware/proxy, providers in layout/page)").option("-i, --interactive", "Interactively choose what to set up (packages, skills, MCP, VS Code extension, LSP, …). Needs a terminal").option("--routing <routing>", "Locale routing: prefix-no-default | prefix-all | no-prefix | search-params | none").option("--content <layout>", "Content declaration layout: multilingual | per-locale | centralized | namespaces").option("--content-format <format>", "Content format, used with --content: ts | json (multilingual, per-locale), json | po (centralized, namespaces)").option("--message-format <format>", "Message format of JSON catalogs, used with --content centralized | namespaces: icu | i18next | vue-i18n | intlayer").action(async (options) => {
|
|
158
|
+
const { init, parseLocaleRoutingChoice, getRoutingInitOptions, getContentInitOptions } = await Promise.resolve().then(() => require("./init.cjs"));
|
|
159
159
|
return init(options.projectRoot, {
|
|
160
160
|
noGitignore: options.gitignore === false,
|
|
161
161
|
noGithubActions: options.githubActions === false,
|
|
162
162
|
noFrameworkSetup: options.frameworkSetup === false,
|
|
163
163
|
upgradeToVersion: packageJson.version,
|
|
164
|
-
...options.routing ? getRoutingInitOptions(parseLocaleRoutingChoice(options.routing)) : {}
|
|
164
|
+
...options.routing ? getRoutingInitOptions(parseLocaleRoutingChoice(options.routing)) : {},
|
|
165
|
+
...getContentInitOptions(options.content, options.contentFormat, options.messageFormat)
|
|
165
166
|
}, options.interactive === true);
|
|
166
167
|
});
|
|
167
|
-
initCmd.command("
|
|
168
|
-
const {
|
|
169
|
-
|
|
168
|
+
initCmd.command("packages").description("Install the missing Intlayer packages and upgrade the outdated ones").option("--project-root [projectRoot]", "Project root directory").action(async (options) => {
|
|
169
|
+
const { initSteps } = await Promise.resolve().then(() => require("./init.cjs"));
|
|
170
|
+
return initSteps(options.projectRoot, ["packages"], { upgradeToVersion: packageJson.version });
|
|
170
171
|
});
|
|
171
|
-
initCmd.command("
|
|
172
|
-
const {
|
|
173
|
-
return
|
|
172
|
+
initCmd.command("project").alias("framework").description("Set up the project: Intlayer config, tsconfig, bundler plugin, middleware/proxy, providers in layout/page and .gitignore").option("--project-root [projectRoot]", "Project root directory").option("--no-gitignore", "Do not add .intlayer to .gitignore").option("--routing <routing>", "Locale routing: prefix-no-default | prefix-all | no-prefix | search-params | none").option("--content <layout>", "Content declaration layout: multilingual | per-locale | centralized | namespaces").option("--content-format <format>", "Content format, used with --content: ts | json (multilingual, per-locale), json | po (centralized, namespaces)").option("--message-format <format>", "Message format of JSON catalogs, used with --content centralized | namespaces: icu | i18next | vue-i18n | intlayer").action(async (options) => {
|
|
173
|
+
const { initSteps, parseLocaleRoutingChoice, getRoutingInitOptions, getContentInitOptions } = await Promise.resolve().then(() => require("./init.cjs"));
|
|
174
|
+
return initSteps(options.projectRoot, ["projectSetup"], {
|
|
175
|
+
noGitignore: options.gitignore === false,
|
|
176
|
+
...options.routing ? getRoutingInitOptions(parseLocaleRoutingChoice(options.routing)) : {},
|
|
177
|
+
...getContentInitOptions(options.content, options.contentFormat, options.messageFormat)
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
initCmd.command("github-actions").alias("ci").description("Scaffold the fill and test GitHub Actions workflows that run on every pull request").option("--project-root [projectRoot]", "Project root directory").action(async (options) => {
|
|
181
|
+
const { initSteps } = await Promise.resolve().then(() => require("./init.cjs"));
|
|
182
|
+
return initSteps(options.projectRoot, ["githubActions"]);
|
|
183
|
+
});
|
|
184
|
+
initCmd.command("vscode-extension").alias("vscode").description("Recommend the Intlayer VS Code extension in .vscode/extensions.json").option("--project-root [projectRoot]", "Project root directory").action(async (options) => {
|
|
185
|
+
const { initSteps } = await Promise.resolve().then(() => require("./init.cjs"));
|
|
186
|
+
return initSteps(options.projectRoot, ["vscodeExtension"]);
|
|
187
|
+
});
|
|
188
|
+
initCmd.command("lsp").description("Configure the Intlayer language server in .vscode/settings.json").option("--project-root [projectRoot]", "Project root directory").action(async (options) => {
|
|
189
|
+
const { initSteps } = await Promise.resolve().then(() => require("./init.cjs"));
|
|
190
|
+
return initSteps(options.projectRoot, ["lsp"]);
|
|
191
|
+
});
|
|
192
|
+
initCmd.command("eslint").alias("lint").description("Enable the Intlayer lint rules (ESLint / oxlint), when the project already lints").option("--project-root [projectRoot]", "Project root directory").action(async (options) => {
|
|
193
|
+
const { initSteps } = await Promise.resolve().then(() => require("./init.cjs"));
|
|
194
|
+
return initSteps(options.projectRoot, ["eslint"], { upgradeToVersion: packageJson.version });
|
|
195
|
+
});
|
|
196
|
+
initCmd.command("skills").description("Initialize Intlayer skills in the project").option("--project-root [projectRoot]", "Project root directory").option("-p, --platform <platform>", "AI platform (e.g. Claude, Cursor, VSCode). Detected when omitted").option("-s, --skills <skills...>", "Skills to install (e.g. Usage Content React). Defaults to the project stack without a terminal").action(async (options) => {
|
|
197
|
+
const { initSkills, parsePlatform, parseSkills } = await Promise.resolve().then(() => require("./initSkills.cjs"));
|
|
198
|
+
await initSkills(options.projectRoot, {
|
|
199
|
+
platform: options.platform ? parsePlatform(options.platform) : void 0,
|
|
200
|
+
skills: options.skills ? parseSkills(options.skills) : void 0
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
initCmd.command("mcp").description("Initialize Intlayer MCP server in the project").option("--project-root [projectRoot]", "Project root directory").option("-p, --platform <platform>", "AI platform (e.g. Claude, Cursor, VSCode). Detected when omitted").option("-t, --transport <transport>", "stdio (local server, default without a terminal) | sse (hosted)").action(async (options) => {
|
|
204
|
+
const { initMCP, parseMCPTransport } = await Promise.resolve().then(() => require("./initMCP.cjs"));
|
|
205
|
+
const { parsePlatform } = await Promise.resolve().then(() => require("./initSkills.cjs"));
|
|
206
|
+
return initMCP(options.projectRoot, {
|
|
207
|
+
platform: options.platform ? parsePlatform(options.platform) : void 0,
|
|
208
|
+
transport: options.transport ? parseMCPTransport(options.transport) : void 0
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
initCmd.command("cms").description("Log in to the Intlayer CMS through your browser, then store the credentials in your .env").option("--project-root [projectRoot]", "Project root directory").action(async (options) => {
|
|
212
|
+
const { initCms } = await Promise.resolve().then(() => require("./init.cjs"));
|
|
213
|
+
return initCms(options.projectRoot);
|
|
214
|
+
});
|
|
215
|
+
initCmd.command("repository").description("Connect the git repository of the project (GitHub, GitLab, Bitbucket, Codeberg, Gitee) to its CMS project and set its build settings, through your browser").option("--project-root [projectRoot]", "Project root directory").action(async (options) => {
|
|
216
|
+
const { initRepository } = await Promise.resolve().then(() => require("./init.cjs"));
|
|
217
|
+
return initRepository(options.projectRoot);
|
|
174
218
|
});
|
|
175
|
-
initCmd.command("infra").description("Set up the Intlayer infrastructure: desktop app, all-in-one Docker container or Docker Compose stack (runs https://intlayer.org/install.sh, or install.ps1 on Windows)").option("-m, --mode <mode>", "Skip the menu: desktop | docker | compose").action(async (options) => {
|
|
219
|
+
initCmd.command("infra").description("Set up the Intlayer infrastructure: desktop app, all-in-one Docker container or Docker Compose stack (runs https://intlayer.org/install.sh, or install.ps1 on Windows)").option("-m, --mode <mode>", "Skip the menu: desktop | docker | compose (required without a terminal)").action(async (options) => {
|
|
176
220
|
const { initInfra, parseInfraMode } = await Promise.resolve().then(() => require("./initInfra.cjs"));
|
|
177
221
|
return initInfra({ mode: options.mode ? parseInfraMode(options.mode) : void 0 });
|
|
178
222
|
});
|
|
179
|
-
initCmd.command("extension").alias("chrome-extension").description("Install the Intlayer Chrome extension or Firefox add-on in your browser").action(async () => {
|
|
180
|
-
const { initChromeExtension } = await Promise.resolve().then(() => require("./initChromeExtension.cjs"));
|
|
181
|
-
return initChromeExtension();
|
|
223
|
+
initCmd.command("extension").alias("chrome-extension").description("Install the Intlayer Chrome extension or Firefox add-on in your browser").option("-b, --browser <browsers...>", "Browsers to open the store page for: chrome | firefox. Without a terminal, the links are only printed").action(async (options) => {
|
|
224
|
+
const { initChromeExtension, parseExtensionBrowsers } = await Promise.resolve().then(() => require("./initChromeExtension.cjs"));
|
|
225
|
+
return initChromeExtension({ browsers: options.browser ? parseExtensionBrowsers(options.browser) : void 0 });
|
|
182
226
|
});
|
|
183
227
|
/**
|
|
184
228
|
* UPGRADE
|
|
@@ -404,7 +448,7 @@ const setAPI = () => {
|
|
|
404
448
|
const keys = [...options.keys ?? [], ...options.key ?? []];
|
|
405
449
|
const excludedKeys = [...options.excludedKeys ?? [], ...options.excludedKey ?? []];
|
|
406
450
|
const dictionaries = [...options.dictionaries ?? [], ...options.dictionary ?? []];
|
|
407
|
-
|
|
451
|
+
await fill({
|
|
408
452
|
...options,
|
|
409
453
|
keys: keys.length > 0 ? keys : void 0,
|
|
410
454
|
excludedKeys: excludedKeys.length > 0 ? excludedKeys : void 0,
|
|
@@ -573,6 +617,7 @@ const setAPI = () => {
|
|
|
573
617
|
const { runCI } = await Promise.resolve().then(() => require("./ci.cjs"));
|
|
574
618
|
process.exit(await runCI(args));
|
|
575
619
|
});
|
|
620
|
+
program.enablePositionalOptions();
|
|
576
621
|
program.parse(process.argv);
|
|
577
622
|
return program;
|
|
578
623
|
};
|