@intlayer/cli 9.6.0 → 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.
Files changed (66) hide show
  1. package/dist/cjs/IntlayerEventListener.cjs.map +1 -1
  2. package/dist/cjs/auth/login.cjs +11 -0
  3. package/dist/cjs/auth/login.cjs.map +1 -1
  4. package/dist/cjs/auth/setupRepository.cjs +95 -0
  5. package/dist/cjs/auth/setupRepository.cjs.map +1 -0
  6. package/dist/cjs/cli.cjs +12 -6
  7. package/dist/cjs/cli.cjs.map +1 -1
  8. package/dist/cjs/editor.cjs +61 -32
  9. package/dist/cjs/editor.cjs.map +1 -1
  10. package/dist/cjs/fill/fill.cjs.map +1 -1
  11. package/dist/cjs/fill/translateDictionary.cjs.map +1 -1
  12. package/dist/cjs/index.cjs +14 -0
  13. package/dist/cjs/init.cjs +165 -11
  14. package/dist/cjs/init.cjs.map +1 -1
  15. package/dist/cjs/liveSync.cjs.map +1 -1
  16. package/dist/cjs/reviewDoc/reviewDoc.cjs.map +1 -1
  17. package/dist/cjs/reviewDoc/reviewDocBlockAware.cjs.map +1 -1
  18. package/dist/cjs/utils/checkAccess.cjs.map +1 -1
  19. package/dist/cjs/utils/detectGitRepository.cjs +87 -0
  20. package/dist/cjs/utils/detectGitRepository.cjs.map +1 -0
  21. package/dist/esm/IntlayerEventListener.mjs.map +1 -1
  22. package/dist/esm/auth/login.mjs +11 -0
  23. package/dist/esm/auth/login.mjs.map +1 -1
  24. package/dist/esm/auth/setupRepository.mjs +90 -0
  25. package/dist/esm/auth/setupRepository.mjs.map +1 -0
  26. package/dist/esm/cli.mjs +12 -6
  27. package/dist/esm/cli.mjs.map +1 -1
  28. package/dist/esm/editor.mjs +62 -34
  29. package/dist/esm/editor.mjs.map +1 -1
  30. package/dist/esm/fill/fill.mjs.map +1 -1
  31. package/dist/esm/fill/translateDictionary.mjs.map +1 -1
  32. package/dist/esm/index.mjs +7 -3
  33. package/dist/esm/init.mjs +163 -13
  34. package/dist/esm/init.mjs.map +1 -1
  35. package/dist/esm/liveSync.mjs.map +1 -1
  36. package/dist/esm/reviewDoc/reviewDoc.mjs.map +1 -1
  37. package/dist/esm/reviewDoc/reviewDocBlockAware.mjs.map +1 -1
  38. package/dist/esm/utils/checkAccess.mjs.map +1 -1
  39. package/dist/esm/utils/detectGitRepository.mjs +85 -0
  40. package/dist/esm/utils/detectGitRepository.mjs.map +1 -0
  41. package/dist/types/IntlayerEventListener.d.ts +1 -1
  42. package/dist/types/IntlayerEventListener.d.ts.map +1 -1
  43. package/dist/types/auth/login.d.ts +5 -0
  44. package/dist/types/auth/login.d.ts.map +1 -1
  45. package/dist/types/auth/setupRepository.d.ts +25 -0
  46. package/dist/types/auth/setupRepository.d.ts.map +1 -0
  47. package/dist/types/editor.d.ts +10 -0
  48. package/dist/types/editor.d.ts.map +1 -1
  49. package/dist/types/fill/fill.d.ts +1 -1
  50. package/dist/types/fill/translateDictionary.d.ts +1 -2
  51. package/dist/types/fill/translateDictionary.d.ts.map +1 -1
  52. package/dist/types/index.d.ts +7 -3
  53. package/dist/types/init.d.ts +19 -2
  54. package/dist/types/init.d.ts.map +1 -1
  55. package/dist/types/liveSync.d.ts.map +1 -1
  56. package/dist/types/reviewDoc/reviewDoc.d.ts +1 -1
  57. package/dist/types/reviewDoc/reviewDoc.d.ts.map +1 -1
  58. package/dist/types/reviewDoc/reviewDocBlockAware.d.ts +1 -2
  59. package/dist/types/reviewDoc/reviewDocBlockAware.d.ts.map +1 -1
  60. package/dist/types/translateDoc/types.d.ts +1 -1
  61. package/dist/types/utils/checkAccess.d.ts +2 -1
  62. package/dist/types/utils/checkAccess.d.ts.map +1 -1
  63. package/dist/types/utils/detectGitRepository.d.ts +34 -0
  64. package/dist/types/utils/detectGitRepository.d.ts.map +1 -0
  65. package/dist/types/utils/selectCmsEnvironment.d.ts +1 -1
  66. package/package.json +11 -11
@@ -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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"}
@@ -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/cli.cjs CHANGED
@@ -154,25 +154,27 @@ const setAPI = () => {
154
154
  /**
155
155
  * INIT
156
156
  */
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").action(async (options) => {
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
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) => {
168
169
  const { initSteps } = await Promise.resolve().then(() => require("./init.cjs"));
169
170
  return initSteps(options.projectRoot, ["packages"], { upgradeToVersion: packageJson.version });
170
171
  });
171
- 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").action(async (options) => {
172
- const { initSteps, parseLocaleRoutingChoice, getRoutingInitOptions } = await Promise.resolve().then(() => require("./init.cjs"));
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"));
173
174
  return initSteps(options.projectRoot, ["projectSetup"], {
174
175
  noGitignore: options.gitignore === false,
175
- ...options.routing ? getRoutingInitOptions(parseLocaleRoutingChoice(options.routing)) : {}
176
+ ...options.routing ? getRoutingInitOptions(parseLocaleRoutingChoice(options.routing)) : {},
177
+ ...getContentInitOptions(options.content, options.contentFormat, options.messageFormat)
176
178
  });
177
179
  });
178
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) => {
@@ -210,6 +212,10 @@ const setAPI = () => {
210
212
  const { initCms } = await Promise.resolve().then(() => require("./init.cjs"));
211
213
  return initCms(options.projectRoot);
212
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);
218
+ });
213
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) => {
214
220
  const { initInfra, parseInfraMode } = await Promise.resolve().then(() => require("./initInfra.cjs"));
215
221
  return initInfra({ mode: options.mode ? parseInfraMode(options.mode) : void 0 });