@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.
Files changed (131) hide show
  1. package/dist/cjs/IntlayerEventListener.cjs.map +1 -1
  2. package/dist/cjs/_virtual/_utils_asset.cjs +1 -1
  3. package/dist/cjs/auth/login.cjs +11 -0
  4. package/dist/cjs/auth/login.cjs.map +1 -1
  5. package/dist/cjs/auth/setupRepository.cjs +95 -0
  6. package/dist/cjs/auth/setupRepository.cjs.map +1 -0
  7. package/dist/cjs/ci.cjs +1 -1
  8. package/dist/cjs/cli.cjs +59 -14
  9. package/dist/cjs/cli.cjs.map +1 -1
  10. package/dist/cjs/editor.cjs +61 -32
  11. package/dist/cjs/editor.cjs.map +1 -1
  12. package/dist/cjs/extract.cjs +1 -1
  13. package/dist/cjs/fill/fill.cjs +25 -9
  14. package/dist/cjs/fill/fill.cjs.map +1 -1
  15. package/dist/cjs/fill/translateDictionary.cjs.map +1 -1
  16. package/dist/cjs/getTargetDictionary.cjs +1 -1
  17. package/dist/cjs/index.cjs +29 -1
  18. package/dist/cjs/init.cjs +261 -36
  19. package/dist/cjs/init.cjs.map +1 -1
  20. package/dist/cjs/initChromeExtension.cjs +26 -3
  21. package/dist/cjs/initChromeExtension.cjs.map +1 -1
  22. package/dist/cjs/initInfra.cjs +6 -0
  23. package/dist/cjs/initInfra.cjs.map +1 -1
  24. package/dist/cjs/initMCP.cjs +18 -7
  25. package/dist/cjs/initMCP.cjs.map +1 -1
  26. package/dist/cjs/initSkills.cjs +37 -34
  27. package/dist/cjs/initSkills.cjs.map +1 -1
  28. package/dist/cjs/listContentDeclaration.cjs +1 -1
  29. package/dist/cjs/listProjects.cjs +1 -1
  30. package/dist/cjs/liveSync.cjs.map +1 -1
  31. package/dist/cjs/pull.cjs +1 -1
  32. package/dist/cjs/push/push.cjs +1 -1
  33. package/dist/cjs/reviewDoc/reviewDoc.cjs +1 -1
  34. package/dist/cjs/reviewDoc/reviewDoc.cjs.map +1 -1
  35. package/dist/cjs/reviewDoc/reviewDocBlockAware.cjs +1 -1
  36. package/dist/cjs/reviewDoc/reviewDocBlockAware.cjs.map +1 -1
  37. package/dist/cjs/translateDoc/translateDoc.cjs +1 -1
  38. package/dist/cjs/upgrade.cjs +1 -1
  39. package/dist/cjs/utils/checkAccess.cjs.map +1 -1
  40. package/dist/cjs/utils/detectGitRepository.cjs +87 -0
  41. package/dist/cjs/utils/detectGitRepository.cjs.map +1 -0
  42. package/dist/cjs/utils/getParentPackageJSON.cjs +1 -1
  43. package/dist/cjs/utils/isInteractiveTerminal.cjs +12 -0
  44. package/dist/cjs/utils/isInteractiveTerminal.cjs.map +1 -0
  45. package/dist/cjs/utils/parseChoice.cjs +21 -0
  46. package/dist/cjs/utils/parseChoice.cjs.map +1 -0
  47. package/dist/esm/IntlayerEventListener.mjs.map +1 -1
  48. package/dist/esm/_virtual/_utils_asset.mjs +1 -1
  49. package/dist/esm/auth/login.mjs +11 -0
  50. package/dist/esm/auth/login.mjs.map +1 -1
  51. package/dist/esm/auth/setupRepository.mjs +90 -0
  52. package/dist/esm/auth/setupRepository.mjs.map +1 -0
  53. package/dist/esm/ci.mjs +1 -1
  54. package/dist/esm/cli.mjs +59 -14
  55. package/dist/esm/cli.mjs.map +1 -1
  56. package/dist/esm/editor.mjs +62 -34
  57. package/dist/esm/editor.mjs.map +1 -1
  58. package/dist/esm/extract.mjs +1 -1
  59. package/dist/esm/fill/fill.mjs +25 -9
  60. package/dist/esm/fill/fill.mjs.map +1 -1
  61. package/dist/esm/fill/translateDictionary.mjs.map +1 -1
  62. package/dist/esm/getTargetDictionary.mjs +1 -1
  63. package/dist/esm/index.mjs +10 -5
  64. package/dist/esm/init.mjs +253 -38
  65. package/dist/esm/init.mjs.map +1 -1
  66. package/dist/esm/initChromeExtension.mjs +24 -4
  67. package/dist/esm/initChromeExtension.mjs.map +1 -1
  68. package/dist/esm/initInfra.mjs +6 -0
  69. package/dist/esm/initInfra.mjs.map +1 -1
  70. package/dist/esm/initMCP.mjs +21 -11
  71. package/dist/esm/initMCP.mjs.map +1 -1
  72. package/dist/esm/initSkills.mjs +37 -36
  73. package/dist/esm/initSkills.mjs.map +1 -1
  74. package/dist/esm/listContentDeclaration.mjs +1 -1
  75. package/dist/esm/listProjects.mjs +1 -1
  76. package/dist/esm/liveSync.mjs.map +1 -1
  77. package/dist/esm/pull.mjs +1 -1
  78. package/dist/esm/push/push.mjs +1 -1
  79. package/dist/esm/reviewDoc/reviewDoc.mjs +1 -1
  80. package/dist/esm/reviewDoc/reviewDoc.mjs.map +1 -1
  81. package/dist/esm/reviewDoc/reviewDocBlockAware.mjs +1 -1
  82. package/dist/esm/reviewDoc/reviewDocBlockAware.mjs.map +1 -1
  83. package/dist/esm/translateDoc/translateDoc.mjs +1 -1
  84. package/dist/esm/upgrade.mjs +1 -1
  85. package/dist/esm/utils/checkAccess.mjs.map +1 -1
  86. package/dist/esm/utils/detectGitRepository.mjs +85 -0
  87. package/dist/esm/utils/detectGitRepository.mjs.map +1 -0
  88. package/dist/esm/utils/getParentPackageJSON.mjs +1 -1
  89. package/dist/esm/utils/isInteractiveTerminal.mjs +11 -0
  90. package/dist/esm/utils/isInteractiveTerminal.mjs.map +1 -0
  91. package/dist/esm/utils/parseChoice.mjs +20 -0
  92. package/dist/esm/utils/parseChoice.mjs.map +1 -0
  93. package/dist/types/IntlayerEventListener.d.ts +1 -1
  94. package/dist/types/IntlayerEventListener.d.ts.map +1 -1
  95. package/dist/types/auth/login.d.ts +5 -0
  96. package/dist/types/auth/login.d.ts.map +1 -1
  97. package/dist/types/auth/setupRepository.d.ts +25 -0
  98. package/dist/types/auth/setupRepository.d.ts.map +1 -0
  99. package/dist/types/editor.d.ts +10 -0
  100. package/dist/types/editor.d.ts.map +1 -1
  101. package/dist/types/fill/fill.d.ts +23 -2
  102. package/dist/types/fill/fill.d.ts.map +1 -1
  103. package/dist/types/fill/index.d.ts +2 -2
  104. package/dist/types/fill/translateDictionary.d.ts +1 -2
  105. package/dist/types/fill/translateDictionary.d.ts.map +1 -1
  106. package/dist/types/index.d.ts +11 -6
  107. package/dist/types/init.d.ts +55 -2
  108. package/dist/types/init.d.ts.map +1 -1
  109. package/dist/types/initChromeExtension.d.ts +19 -1
  110. package/dist/types/initChromeExtension.d.ts.map +1 -1
  111. package/dist/types/initInfra.d.ts.map +1 -1
  112. package/dist/types/initMCP.d.ts +16 -2
  113. package/dist/types/initMCP.d.ts.map +1 -1
  114. package/dist/types/initSkills.d.ts +23 -6
  115. package/dist/types/initSkills.d.ts.map +1 -1
  116. package/dist/types/liveSync.d.ts.map +1 -1
  117. package/dist/types/reviewDoc/reviewDoc.d.ts +1 -1
  118. package/dist/types/reviewDoc/reviewDoc.d.ts.map +1 -1
  119. package/dist/types/reviewDoc/reviewDocBlockAware.d.ts +1 -2
  120. package/dist/types/reviewDoc/reviewDocBlockAware.d.ts.map +1 -1
  121. package/dist/types/translateDoc/types.d.ts +1 -1
  122. package/dist/types/utils/checkAccess.d.ts +2 -1
  123. package/dist/types/utils/checkAccess.d.ts.map +1 -1
  124. package/dist/types/utils/detectGitRepository.d.ts +34 -0
  125. package/dist/types/utils/detectGitRepository.d.ts.map +1 -0
  126. package/dist/types/utils/isInteractiveTerminal.d.ts +9 -0
  127. package/dist/types/utils/isInteractiveTerminal.d.ts.map +1 -0
  128. package/dist/types/utils/parseChoice.d.ts +13 -0
  129. package/dist/types/utils/parseChoice.d.ts.map +1 -0
  130. package/dist/types/utils/selectCmsEnvironment.d.ts +1 -1
  131. 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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiCA,IAAa,wBAAb,MAAmC;CAoCvB;CAnCV,AAAQ,gBAAYA,sCAAa,EAAE,gCAAI,CAAC;CAExC,AAAQ,cAAkC;CAC1C,AAAQ,oBAAoB;CAC5B,AAAQ,uBAAuB;CAC/B,AAAQ,iBAAiB;CACzB,AAAQ,yBAAyB;CACjC,AAAQ,mBAA0C;;;;CAKlD,AAAO;;;;CAKP,AAAO;;;;CAKP,AAAO;;;;CAKP,AAAO;;;;CAKP,AAAO;CAEP,YACE,AAAQ,iBAAyD;EAC/D;EACA;CACF,GACA;EAJQ;EAKR,KAAK,gBAAYA,sCAAa,KAAK,cAAc;CACnD;;;;;CAMA,MAAa,aAA4B;EACvC,KAAK,yBAAyB;EAC9B,MAAM,KAAK,QAAQ;CACrB;;;;CAKA,MAAc,UAAyB;EACrC,IAAI;GACF,MAAM,aAAa,KAAK,eAAe,OAAO;GAG9C,MAAM,cAAc,UAAMC,mCACxB,QACA,KAAK,cACP,CAAC,CAAC,MAAM,qBAAqB;GAE7B,IAAI,CAAC,aACH,MAAM,IAAI,MAAM,iCAAiC;GAGnD,MAAM,YAAY,GAAG,WAAW;GAGhC,IAAI,KAAK,aACP,KAAK,YAAY,MAAM;GAGzB,KAAK,cAAc,IAAIC,wBAAY,WAAW,EAC5C,QAAQ,OAAO,SACb,MAAM,OAAO;IACX,GAAG;IACH,SAAS;KACP,GAAI,MAAM,WAAW,CAAC;KACtB,eAAe,UAAU,YAAY,MAAM;IAC7C;GACF,CAAC,EACL,CAAC;GAED,KAAK,YAAY,eAAe;IAC9B,KAAK,oBAAoB;IACzB,KAAK,iBAAiB;IACtB,KAAK,mBAAmB;GAC1B;GAEA,KAAK,YAAY,aAAa,UAAU,KAAK,cAAc,KAAK;GAChE,KAAK,YAAY,WAAW,UAAU,KAAK,YAAY,KAAK;EAC9D,SAAS,QAAQ;GACf,KAAK,UAAU,+CAA+C,EAC5D,OAAO,QACT,CAAC;GACD,KAAK,kBAAkB;EACzB;CACF;;;;CAKA,AAAO,UAAgB;EACrB,KAAK,yBAAyB;EAE9B,IAAI,KAAK,kBAAkB;GACzB,aAAa,KAAK,gBAAgB;GAClC,KAAK,mBAAmB;EAC1B;EAEA,IAAI,KAAK,aAAa;GACpB,KAAK,YAAY,MAAM;GACvB,KAAK,cAAc;EACrB;CACF;;;;CAKA,AAAQ,oBAA0B;EAChC,IACE,KAAK,0BACL,KAAK,qBAAqB,KAAK,sBAC/B;GACA,IAAI,KAAK,qBAAqB,KAAK,sBACjC,KAAK,UACH,CACE,8BAA8B,KAAK,qBAAqB,sBAC1D,GACA,EACE,OAAO,QACT,CACF;GAEF;EACF;EAEA,KAAK;EACL,MAAM,QAAQ,KAAK,iBAAiB,MAAM,KAAK,oBAAoB;EAEnE,KAAK,UACH,mCAAmC,KAAK,kBAAkB,GAAG,KAAK,qBAAqB,MAAM,MAAM,GACrG;EAEA,KAAK,mBAAmB,WAAW,YAAY;GAC7C,IAAI,CAAC,KAAK,wBACR,MAAM,KAAK,QAAQ;EAEvB,GAAG,KAAK;CACV;;;;;CAMA,MAAc,cAAc,OAA4C;EACtE,IAAI;GACF,MAAM,EAAE,SAAS;GAEjB,MAAM,WAA+B,KAAK,MAAM,IAAI;GAEpD,KAAK,MAAM,UAAU,UACnB,QAAQ,OAAO,QAAf;IACE,KAAK;KACH,QAAQ,OAAO,QAAf;MACE,KAAK;OACH,MAAM,KAAK,oBAAoB,OAAO,IAAI;OAC1C;MACF,KAAK;OACH,MAAM,KAAK,qBAAqB,OAAO,IAAI;OAC3C;MACF,KAAK;OACH,MAAM,KAAK,sBAAsB,OAAO,IAAI;OAC5C;MACF,SACE,KAAK,UACH,CAAC,gCAAgC,OAAO,MAAM,GAC9C,EACE,OAAO,QACT,CACF;KAEJ;KACA;IACF,SACE,KAAK,UAAU,CAAC,wBAAwB,OAAO,MAAM,GAAG,EACtD,OAAO,QACT,CAAC;GAEL;EAEJ,SAAS,OAAO;GACd,KAAK,UAAU,CAAC,uCAAuC,KAAK,GAAG,EAC7D,OAAO,QACT,CAAC;EACH;CACF;;;;CAKA,AAAQ,YAAY,OAAoB;EACtC,MAAM,aAAa;EAGnB,KAAK,UACH,CACE,sBACA;GACE,MAAM,WAAW;GACjB,SAAS,WAAW;GACpB,MAAM,WAAW;GACjB,YAAY,KAAK,aAAa;GAC9B,KAAK,KAAK,aAAa;EACzB,CACF,GACA,EACE,OAAO,QACT,CACF;EAGA,KAAK,oBAAoB,KAAK;EAS9B,IALE,WAAW,SAAS,YACnB,WAAW,SAAS,SAAS,YAAY,KACxC,WAAW,SAAS,SAAS,QAAQ,KACrC,KAAK,aAAa,eAAeA,wBAAY,WAEvB,CAAC,KAAK,wBAAwB;GACtD,KAAK,UACH,iEACF;GACA,KAAK,kBAAkB;EACzB,OAEE,KAAK,QAAQ;CAEjB;AACF"}
@@ -1,5 +1,5 @@
1
- let node_path = require("node:path");
2
1
  let node_fs = require("node:fs");
2
+ let node_path = require("node:path");
3
3
  let node_url = require("node:url");
4
4
 
5
5
  //#region \0utils:asset
@@ -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":";;;;;;;;;;;;;;AAYA,MAAM,oBAAoB,YAA4B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;aA2CzC,QAAQ;;;;;;;;;AAoCrB,MAAa,QAAQ,OAAO,UAAwB,CAAC,MAAM;CACzD,MAAM,oBAAgBA,wCAAiB,QAAQ,aAAa;CAC5D,2CAAiB,SAAS,aAAa;CAEvC,MAAM,aAASC,sCAAa,aAAa;CAEzC,MAAM,SAAS,QAAQ,UAAU,cAAc,OAAO;CAEtD,OAAO,IAAI,SAAe,YAAY;;;;;;EAMpC,MAAM,iBACJ,UACA,YACS;GACT,SAAS,UAAU,KAAK;IACtB,gBAAgB;IAChB,YAAY;GACd,CAAC;GACD,SAAS,IAAI,iBAAiB,OAAO,SAAS;IAC5C,OAAO,MAAM;IACb,OAAO,oBAAoB;IAC3B,QAAQ;IAER,IAAI,QAAQ,cAAc,OACxB,QAAQ,KAAK,CAAC;GAElB,CAAC;EACH;EAEA,MAAM,SAASC,kBAAK,aAAa,OAAO,KAAK,QAAQ;GACnD,MAAM,MAAM,IAAIC,aAAI,IAAI,OAAO,IAAI,UAAU,IAAI,QAAQ,MAAM;GAG/D,IAAI,UAAU,+BAA+B,GAAG;GAChD,IAAI,UAAU,gCAAgC,cAAc;GAC5D,IAAI,UAAU,gCAAgC,cAAc;GAE5D,IAAI,IAAI,WAAW,WAAW;IAC5B,IAAI,UAAU,GAAG;IACjB,IAAI,IAAI;IACR;GACF;GAEA,IAAI,IAAI,aAAa,aAAa;IAChC,MAAM,eAAe,IAAI,aAAa,IAAI,cAAc;IACxD,MAAM,mBAAmB,IAAI,aAAa,IAAI,WAAW;IACzD,MAAM,WAAW,IAAI,aAAa,IAAI,UAAU;IAChD,MAAM,eAAe,IAAI,aAAa,IAAI,cAAc;IAExD,IAAI,gBAAgB,kBAAkB;KACpC,OAAO,EAAE;KACT,OACE,0BAAsBC,kCAAS,MAAMC,wBAAW,IAAI,EAAE,yBACxD;KACA,OAAO,EAAE;KAET,WACED,kCACE,qBAAqB,IAAI,KAAK,gBAAgB,CAAC,CAAC,eAAe,KAC/DC,wBAAW,IACb,CACF;KAEA,MAAMC,+CACJ,eACA,cACA,IAAI,KAAK,gBAAgB,CAC3B;KAEA,IAAI,QAAQ,eACV,OACE,wIACA,EAAE,OAAO,OAAO,CAClB;KAGF,cACE,KACA,gGACF;KACA;IACF;IAEA,IAAI,YAAY,cAAc;KAC5B,IAAI,QAAQ,eAAe;MAGzB,OAAO,EAAE;MACT,OAAO,gDAAgD;MAEvD,IAAI;OACF,MAAM,QAAQ,cAAc;QAAE;QAAU;OAAa,CAAC;MACxD,SAAS,OAAO;OACd,OACE,wDACE,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,KAEvD,EAAE,OAAO,QAAQ,CACnB;MACF;KACF,OAAO;MACL,OAAO,EAAE;MACT,OAAO,0DAA0D;MAEjE,OAAO,EAAE;MACT,OAAO;OACL;WACAC,sCAAa,MAAM;OACnB;MACF,CAAC;MACD,WACEH,kCAAS,oCAAoCC,wBAAW,SAAS,CACnE;MACA,OACE,KACED,kCAAS,uBAAuBC,wBAAW,UAAU,OACrDD,kCAAS,UAAUC,wBAAW,IAAI,CACpC,CAAC,CAAC,KAAK,EAAE,CACX;MACA,OACE,KACED,kCAAS,2BAA2BC,wBAAW,UAAU,OACzDD,kCAAS,cAAcC,wBAAW,IAAI,CACxC,CAAC,CAAC,KAAK,EAAE,CACX;MACA,WACED,kCAAS,oCAAoCC,wBAAW,SAAS,CACnE;MACA,OAAO,EAAE;MACT,OAAO,gDAAgD;MACvD,WACED,kCAAS,oCAAoCC,wBAAW,SAAS,CACnE;MACA;OACE,GAAGA,wBAAW,WAAW;OACzB;OACA;OACA,qBAAiBE,sCAAa,QAAS,QAAWF,wBAAW,UAAU,EAAE;OACzE,uBAAmBD,kCAAS,kCAAkCC,wBAAW,MAAMA,wBAAW,UAAU,EAAE;OACtG,2BAAuBD,kCAAS,sCAAsCC,wBAAW,MAAMA,wBAAW,UAAU,EAAE;OAC9G;OACA;MACF,CAAC,CAAC,SAAS,SAAS;OAClB,OAAO,IAAI;MACb,CAAC;MACD,WACED,kCAAS,oCAAoCC,wBAAW,SAAS,CACnE;KACF;KAEA,cACE,KACA,0GACF;IACF,OAAO;KACL,IAAI,UAAU,KAAK,EAAE,gBAAgB,aAAa,CAAC;KACnD,IAAI,IAAI,oBAAoB;IAC9B;GACF,OAAO;IACL,IAAI,UAAU,KAAK,EAAE,gBAAgB,aAAa,CAAC;IACnD,IAAI,IAAI,WAAW;GACrB;EACF,CAAC;EAED,MAAM,mBAAyB;GAC7B,OAAO,MAAM;GACb,OAAO,oBAAoB;GAC3B,QAAQ;EACV;EAEA,IAAI,QAAQ,QAAQ,SAAS;GAC3B,QAAQ;GACR;EACF;EAEA,QAAQ,QAAQ,iBAAiB,SAAS,YAAY,EAAE,MAAM,KAAK,CAAC;EACpE,OAAO,GAAG,eACR,QAAQ,QAAQ,oBAAoB,SAAS,UAAU,CACzD;EAEA,OAAO,OAAO,SAAS;GACrB,MAAM,UAAU,OAAO,QAAQ;GAC/B,MAAM,OAAO,OAAO,YAAY,YAAY,UAAU,QAAQ,OAAO;GACrE,MAAM,QAAQ,KAAK,OAAO,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,UAAU,CAAC;GAEpD,MAAM,aACJ,UAAU,QAAQ,IAAI,qBAAqB;GAC7C,MAAM,aAAa,cAAc,OAAO,cAAc;GACtD,MAAM,WAAW,GAAG,WAAW,uBAAuB,KAAK,SAAS,MAAM,cAAc,mBAAmB,UAAU;GAErH,OAAO,8BAA8B;GACrC,OAAO,wCAAoCE,sCAAa,QAAQ,GAAG;GAEnE,sCAAY,QAAQ;EACtB,CAAC;CACH,CAAC;AACH"}
@@ -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("Initialize Intlayer in the project").option("--project-root [projectRoot]", "Project root directory").option("--no-gitignore", "Do not add .intlayer to .gitignore").option("--no-github-actions", "Do not scaffold the fill and test GitHub Actions workflows").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, …)").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
- initCmd.command("skills").description("Initialize Intlayer skills in the project").option("--project-root [projectRoot]", "Project root directory").action(async (options) => {
168
- const { initSkills } = await Promise.resolve().then(() => require("./initSkills.cjs"));
169
- await initSkills(options.projectRoot);
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("mcp").description("Initialize Intlayer MCP server in the project").option("--project-root [projectRoot]", "Project root directory").action(async (options) => {
172
- const { initMCP } = await Promise.resolve().then(() => require("./initMCP.cjs"));
173
- return initMCP(options.projectRoot);
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
- return fill({
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
  };