@intlayer/engine 9.5.0 → 9.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/init/index.cjs.map +1 -1
- package/dist/cjs/init/utils/devScript.cjs +2 -3
- package/dist/cjs/init/utils/devScript.cjs.map +1 -1
- package/dist/cjs/init/utils/githubActions.cjs +17 -16
- package/dist/cjs/init/utils/githubActions.cjs.map +1 -1
- package/dist/cjs/utils/contentWatcherLock.cjs +4 -5
- package/dist/cjs/utils/contentWatcherLock.cjs.map +1 -1
- package/dist/esm/init/index.mjs.map +1 -1
- package/dist/esm/init/utils/devScript.mjs +2 -3
- package/dist/esm/init/utils/devScript.mjs.map +1 -1
- package/dist/esm/init/utils/githubActions.mjs +17 -16
- package/dist/esm/init/utils/githubActions.mjs.map +1 -1
- package/dist/esm/utils/contentWatcherLock.mjs +4 -5
- package/dist/esm/utils/contentWatcherLock.mjs.map +1 -1
- package/dist/types/formatDictionary.d.ts +3 -2
- package/dist/types/formatDictionary.d.ts.map +1 -1
- package/dist/types/init/utils/devScript.d.ts +2 -3
- package/dist/types/init/utils/devScript.d.ts.map +1 -1
- package/dist/types/init/utils/githubActions.d.ts +9 -11
- package/dist/types/init/utils/githubActions.d.ts.map +1 -1
- package/dist/types/intlayer/dist/types/index.d.ts +14 -0
- package/dist/types/listDictionariesPath.d.ts +1 -1
- package/dist/types/utils/contentWatcherLock.d.ts +2 -13
- package/dist/types/utils/contentWatcherLock.d.ts.map +1 -1
- package/package.json +11 -11
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"githubActions.mjs","names":[],"sources":["../../../../src/init/utils/githubActions.ts"],"sourcesContent":["import { existsSync, readFileSync } from 'node:fs';\nimport { join, relative, resolve, sep } from 'node:path';\nimport { getGitRootDir, listProjects } from '../../listProjects';\nimport { detectPackageManager, type PackageManager } from './packageManager';\n\n/** A GitHub Actions workflow file to be written to the project. */\nexport type GithubWorkflowFile = {\n /** Path relative to the project root, e.g. `.github/workflows/intlayer-fill.yml`. */\n filePath: string;\n /** Full YAML content of the workflow file. */\n content: string;\n};\n\n/** Options controlling how the CI workflows are generated. */\nexport type GithubWorkflowsOptions = {\n /**\n * Route the intlayer commands through `intlayer ci` instead of calling them\n * directly. The `ci` command discovers every Intlayer project of the\n * repository (via `listProjects`) and runs the given command inside each of\n * them, injecting per-project credentials from the\n * `INTLAYER_PROJECT_CREDENTIALS` secret when provided. Required for\n * monorepos where a single workflow must cover several projects.\n */\n useCiCommand?: boolean;\n /**\n * Repository-relative directory (posix separators) the workflow commands run\n * in. Set when the Intlayer project lives in a subdirectory of the\n * repository and the repository root has no workspace manifest to install\n * dependencies from. Undefined = repository root.\n */\n workingDirectory?: string;\n};\n\n/**\n * Resolved placement and generation parameters for the CI workflows.\n * See {@link resolveGithubWorkflowsContext}.\n */\nexport type GithubWorkflowsContext = {\n /**\n * Directory the workflow files must be written to. GitHub only triggers\n * workflows stored in `.github/workflows` at the repository root, so this is\n * the git root whenever the project lives inside a git repository.\n */\n workflowsRootDir: string;\n /** Package manager whose commands are baked into the workflows. */\n packageManager: PackageManager;\n /** Generation options forwarded to {@link getGithubWorkflows}. */\n options: GithubWorkflowsOptions;\n};\n\n/**\n * Returns true when the repository root hosts a workspace manifest\n * (a `package.json` `workspaces` field or a `pnpm-workspace.yaml`), meaning a\n * single install at the root covers every package of the monorepo.\n */\nconst hasWorkspaceManifest = (repositoryRootDir: string): boolean => {\n if (existsSync(join(repositoryRootDir, 'pnpm-workspace.yaml'))) {\n return true;\n }\n\n try {\n const packageJsonPath = join(repositoryRootDir, 'package.json');\n if (!existsSync(packageJsonPath)) return false;\n const { workspaces } = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));\n return Boolean(workspaces);\n } catch {\n return false;\n }\n};\n\n/**\n * Resolves where the CI workflows must be written and how their commands must\n * be generated, based on the repository layout around `rootDir` (the Intlayer\n * project being initialized):\n *\n * - Project at the repository root: workflows are written in place. When the\n * repository is a monorepo (workspace manifest, or several Intlayer projects\n * discovered by `listProjects`), the commands go through `intlayer ci` so\n * every project is covered.\n * - Project nested in a repository whose root has a `package.json`\n * (workspace-managed monorepo): workflows are written at the git root —\n * GitHub ignores `.github/workflows` in subdirectories — dependencies are\n * installed at the root with the root's package manager, and the commands go\n * through `intlayer ci` to iterate every Intlayer project.\n * - Project nested in a repository without a root `package.json`: workflows\n * are written at the git root but run inside the project directory\n * (`working-directory`), keeping the project's own package manager.\n */\nexport const resolveGithubWorkflowsContext = async (\n rootDir: string,\n projectPackageManager: PackageManager\n): Promise<GithubWorkflowsContext> => {\n const projectRootDir = resolve(rootDir);\n\n let repositoryRootDir = projectRootDir;\n try {\n const gitRootDir = await getGitRootDir(projectRootDir);\n if (gitRootDir) repositoryRootDir = resolve(gitRootDir);\n } catch {\n // Not a git repository — keep the project root as the workflow root.\n }\n\n // Discover every Intlayer project of the repository. `listProjects` scans\n // for configuration files, so a fresh project whose config is created later\n // in the init flow may not be counted yet — the workspace manifest check\n // below still catches that monorepo case.\n let repositoryProjectCount = 0;\n try {\n const { projectsPath } = await listProjects({\n baseDir: repositoryRootDir,\n });\n repositoryProjectCount = projectsPath.length;\n } catch {\n repositoryProjectCount = 0;\n }\n\n const isNestedProject = repositoryRootDir !== projectRootDir;\n const isWorkspaceRepository = hasWorkspaceManifest(repositoryRootDir);\n const hasRootPackageJson = existsSync(\n join(repositoryRootDir, 'package.json')\n );\n\n if (!isNestedProject) {\n return {\n workflowsRootDir: projectRootDir,\n packageManager: projectPackageManager,\n options: {\n useCiCommand: isWorkspaceRepository || repositoryProjectCount > 1,\n },\n };\n }\n\n if (hasRootPackageJson) {\n return {\n workflowsRootDir: repositoryRootDir,\n packageManager: detectPackageManager(repositoryRootDir),\n options: { useCiCommand: true },\n };\n }\n\n // Nested project without a root manifest: install and run everything inside\n // the project directory itself.\n return {\n workflowsRootDir: repositoryRootDir,\n packageManager: projectPackageManager,\n options: {\n workingDirectory: relative(repositoryRootDir, projectRootDir)\n .split(sep)\n .join('/'),\n },\n };\n};\n\n/**\n * Package-manager-specific snippets used to assemble the CI workflows.\n * - `setupSteps`: YAML steps (already indented for the `steps:` list) that\n * install the package manager and Node.js, including dependency caching.\n * - `installCommand`: command that installs the project dependencies.\n * - `execCommand`: prefix used to run the locally installed `intlayer` binary.\n */\ntype PackageManagerCIConfig = {\n setupSteps: string;\n installCommand: string;\n execCommand: string;\n};\n\n/** Dependency lock file of each package manager, used for cache invalidation. */\nconst LOCK_FILE_BY_PACKAGE_MANAGER: Record<PackageManager, string> = {\n bun: 'bun.lock',\n pnpm: 'pnpm-lock.yaml',\n yarn: 'yarn.lock',\n npm: 'package-lock.json',\n};\n\n/**\n * Returns the package-manager-specific steps and commands used to build the\n * GitHub Actions workflows. Each package manager needs a slightly different\n * setup action and a different way to run the local `intlayer` binary.\n *\n * `workingDirectory` is the repository-relative directory holding the lock\n * file when the project is nested: `actions/setup-node` looks for the lock\n * file at the repository root by default and fails the cache setup otherwise.\n */\nconst getPackageManagerCIConfig = (\n packageManager: PackageManager,\n workingDirectory?: string\n): PackageManagerCIConfig => {\n const setupNodeStep = (cache: PackageManager | undefined): string => {\n const cacheDependencyPath =\n cache && workingDirectory\n ? `\\n cache-dependency-path: ${workingDirectory}/${LOCK_FILE_BY_PACKAGE_MANAGER[cache]}`\n : '';\n\n return ` - name: 🟢 Setup Node.js\n uses: actions/setup-node@v4\n with:\n node-version: 20${cache ? `\\n cache: ${cache}` : ''}${cacheDependencyPath}`;\n };\n\n switch (packageManager) {\n case 'bun':\n return {\n setupSteps: ` - name: 🥟 Setup Bun\n uses: oven-sh/setup-bun@v2`,\n installCommand: 'bun install --frozen-lockfile',\n execCommand: 'bunx intlayer',\n };\n case 'pnpm':\n return {\n setupSteps: ` - name: 📦 Setup pnpm\n uses: pnpm/action-setup@v4\n${setupNodeStep('pnpm')}`,\n installCommand: 'pnpm install --frozen-lockfile',\n execCommand: 'pnpm exec intlayer',\n };\n case 'yarn':\n return {\n setupSteps: setupNodeStep('yarn'),\n installCommand: 'yarn install --frozen-lockfile',\n execCommand: 'yarn intlayer',\n };\n case 'npm':\n return {\n setupSteps: setupNodeStep('npm'),\n installCommand: 'npm ci',\n execCommand: 'npx intlayer',\n };\n }\n};\n\n/**\n * Renders the `defaults.run.working-directory` block for jobs that must run\n * inside a nested project directory. Empty when the commands run at the\n * repository root.\n */\nconst getWorkingDirectoryBlock = (workingDirectory?: string): string =>\n workingDirectory\n ? `\n defaults:\n run:\n working-directory: ${workingDirectory}`\n : '';\n\n/**\n * Renders the env comment + optional wiring for per-project credentials in\n * monorepo (`intlayer ci`) mode. The `ci` command matches each entry of the\n * `INTLAYER_PROJECT_CREDENTIALS` JSON map to a discovered project path and\n * injects its access keys before running the command in that project.\n */\nconst getMonorepoCredentialsBlock = (useCiCommand?: boolean): string =>\n useCiCommand\n ? `\n #\n # Monorepo — per-project CMS credentials, as a JSON map of project path\n # (relative to the repository root) to access keys, e.g.\n # {\"apps/web\":{\"clientId\":\"...\",\"clientSecret\":\"...\"}}:\n # INTLAYER_PROJECT_CREDENTIALS: \\${{ secrets.INTLAYER_PROJECT_CREDENTIALS }}`\n : '';\n\n/**\n * Builds the `intlayer fill` workflow. Runs on pull requests, regenerates the\n * missing translations for the changed dictionaries (`--git-diff`) using AI,\n * then commits the result back to the PR branch.\n *\n * AI access is required for `fill`. The workflow exposes both options:\n * - a provider API key (`AI_API_KEY` secret), forwarded via CLI flags, or\n * - Intlayer CMS access keys (`INTLAYER_CLIENT_ID` / `INTLAYER_CLIENT_SECRET`).\n */\nconst generateFillWorkflow = (\n packageManager: PackageManager,\n options: GithubWorkflowsOptions\n): string => {\n const { setupSteps, installCommand, execCommand } = getPackageManagerCIConfig(\n packageManager,\n options.workingDirectory\n );\n\n // In monorepo mode `intlayer ci <command>` discovers every Intlayer project\n // of the repository and runs the command inside each of them.\n const intlayerCommand = options.useCiCommand\n ? `${execCommand} ci`\n : execCommand;\n\n return `name: Intlayer Fill\n# Auto-fill missing translations on every pull request.\non:\n pull_request:\n branches:\n - main\n\npermissions:\n contents: write\n pull-requests: write\n\nconcurrency:\n group: intlayer-fill-\\${{ github.ref }}\n cancel-in-progress: true\n\njobs:\n fill:\n runs-on: ubuntu-latest${getWorkingDirectoryBlock(options.workingDirectory)}\n env:\n # AI access is required to generate translations.\n # Add the secret in: Settings → Secrets and variables → Actions.\n #\n # Option 1 — Use your own AI provider key (forwarded below via CLI flags):\n AI_PROVIDER: openai\n AI_MODEL: gpt-5-mini\n AI_API_KEY: \\${{ secrets.AI_API_KEY }}\n #\n # Option 2 — Use Intlayer CMS access keys instead of your own AI key.\n # Wire them in your intlayer.config and uncomment the lines below:\n # INTLAYER_CLIENT_ID: \\${{ secrets.INTLAYER_CLIENT_ID }}\n # INTLAYER_CLIENT_SECRET: \\${{ secrets.INTLAYER_CLIENT_SECRET }}${getMonorepoCredentialsBlock(options.useCiCommand)}\n steps:\n - name: ⬇️ Checkout repository\n uses: actions/checkout@v4\n with:\n persist-credentials: true # Keep credentials to push back to the PR\n fetch-depth: 0 # Full history so --git-diff can compare refs\n${setupSteps}\n - name: 📦 Install dependencies\n run: ${installCommand}\n - name: ⚙️ Build dictionaries\n run: ${intlayerCommand} build\n - name: 🤖 Fill missing translations\n # Skip when no AI credentials are configured, so the workflow stays green\n # until an \\`AI_API_KEY\\` (or Intlayer CMS access keys) secret is added.\n if: \\${{ env.AI_API_KEY != '' || env.INTLAYER_CLIENT_ID != '' }}\n run: ${intlayerCommand} fill --git-diff --mode complete --provider $AI_PROVIDER --model $AI_MODEL --api-key $AI_API_KEY\n - name: 📤 Commit and push changes\n run: |\n git config --local user.email \"github-actions[bot]@users.noreply.github.com\"\n git config --local user.name \"github-actions[bot]\"\n if [ -n \"$(git status --porcelain)\" ]; then\n git add .\n git commit -m \"chore(intlayer): fill missing translations [skip ci]\"\n git push origin HEAD:\\${{ github.head_ref }}\n else\n echo \"No missing translations to fill.\"\n fi\n`;\n};\n\n/**\n * Builds the `intlayer test` workflow. Runs on pull requests and fails the\n * check when required locales are missing translations. No AI access needed.\n */\nconst generateTestWorkflow = (\n packageManager: PackageManager,\n options: GithubWorkflowsOptions\n): string => {\n const { setupSteps, installCommand, execCommand } = getPackageManagerCIConfig(\n packageManager,\n options.workingDirectory\n );\n\n // In monorepo mode `intlayer ci <command>` discovers every Intlayer project\n // of the repository and runs the command inside each of them.\n const intlayerCommand = options.useCiCommand\n ? `${execCommand} ci`\n : execCommand;\n\n return `name: Intlayer Test\n# Fail the pull request when required locales are missing translations.\non:\n pull_request:\n branches:\n - main\n\nconcurrency:\n group: intlayer-test-\\${{ github.ref }}\n cancel-in-progress: true\n\njobs:\n test:\n runs-on: ubuntu-latest${getWorkingDirectoryBlock(options.workingDirectory)}\n steps:\n - name: ⬇️ Checkout repository\n uses: actions/checkout@v4\n${setupSteps}\n - name: 📦 Install dependencies\n run: ${installCommand}\n - name: ⚙️ Build dictionaries\n run: ${intlayerCommand} build\n - name: 🧪 Test for missing translations\n run: ${intlayerCommand} test\n`;\n};\n\n/** Workflow file path constants, relative to the project root. */\nexport const GITHUB_FILL_WORKFLOW_PATH = '.github/workflows/intlayer-fill.yml';\nexport const GITHUB_TEST_WORKFLOW_PATH = '.github/workflows/intlayer-test.yml';\n\n/**\n * Returns the two Intlayer GitHub Actions workflows (`fill` and `test`),\n * generated with commands matching the detected package manager and the\n * repository layout (see {@link GithubWorkflowsOptions} for the monorepo and\n * nested-project variants).\n */\nexport const getGithubWorkflows = (\n packageManager: PackageManager,\n options: GithubWorkflowsOptions = {}\n): GithubWorkflowFile[] => [\n {\n filePath: GITHUB_FILL_WORKFLOW_PATH,\n content: generateFillWorkflow(packageManager, options),\n },\n {\n filePath: GITHUB_TEST_WORKFLOW_PATH,\n content: generateTestWorkflow(packageManager, options),\n },\n];\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"githubActions.mjs","names":[],"sources":["../../../../src/init/utils/githubActions.ts"],"sourcesContent":["import { existsSync, readFileSync } from 'node:fs';\nimport { join, relative, resolve, sep } from 'node:path';\nimport { getGitRootDir, listProjects } from '../../listProjects';\nimport { detectPackageManager, type PackageManager } from './packageManager';\n\n/** A GitHub Actions workflow file to be written to the project. */\nexport type GithubWorkflowFile = {\n /** Path relative to the project root, e.g. `.github/workflows/intlayer-fill.yml`. */\n filePath: string;\n /** Full YAML content of the workflow file. */\n content: string;\n};\n\n/** Options controlling how the CI workflows are generated. */\nexport type GithubWorkflowsOptions = {\n /**\n * The repository hosts several Intlayer projects. Every scaffolded command\n * always carries `--ci` (the CLI discovers each Intlayer project of the\n * repository and runs the command inside it), so this only adds the\n * `INTLAYER_PROJECT_CREDENTIALS` hint to the workflow env: a JSON map of\n * project path to CMS access keys injected per project by the CLI.\n */\n isMonorepo?: boolean;\n /**\n * Repository-relative directory (posix separators) the workflow commands run\n * in. Set when the Intlayer project lives in a subdirectory of the\n * repository and the repository root has no workspace manifest to install\n * dependencies from. Undefined = repository root.\n */\n workingDirectory?: string;\n};\n\n/**\n * Resolved placement and generation parameters for the CI workflows.\n * See {@link resolveGithubWorkflowsContext}.\n */\nexport type GithubWorkflowsContext = {\n /**\n * Directory the workflow files must be written to. GitHub only triggers\n * workflows stored in `.github/workflows` at the repository root, so this is\n * the git root whenever the project lives inside a git repository.\n */\n workflowsRootDir: string;\n /** Package manager whose commands are baked into the workflows. */\n packageManager: PackageManager;\n /** Generation options forwarded to {@link getGithubWorkflows}. */\n options: GithubWorkflowsOptions;\n};\n\n/**\n * Returns true when the repository root hosts a workspace manifest\n * (a `package.json` `workspaces` field or a `pnpm-workspace.yaml`), meaning a\n * single install at the root covers every package of the monorepo.\n */\nconst hasWorkspaceManifest = (repositoryRootDir: string): boolean => {\n if (existsSync(join(repositoryRootDir, 'pnpm-workspace.yaml'))) {\n return true;\n }\n\n try {\n const packageJsonPath = join(repositoryRootDir, 'package.json');\n if (!existsSync(packageJsonPath)) return false;\n const { workspaces } = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));\n return Boolean(workspaces);\n } catch {\n return false;\n }\n};\n\n/**\n * Resolves where the CI workflows must be written and how their commands must\n * be generated, based on the repository layout around `rootDir` (the Intlayer\n * project being initialized):\n *\n * - Project at the repository root: workflows are written in place. When the\n * repository is a monorepo (workspace manifest, or several Intlayer projects\n * discovered by `listProjects`), the per-project credentials hint is added.\n * - Project nested in a repository whose root has a `package.json`\n * (workspace-managed monorepo): workflows are written at the git root —\n * GitHub ignores `.github/workflows` in subdirectories — dependencies are\n * installed at the root with the root's package manager, and the per-project\n * credentials hint is added.\n * - Project nested in a repository without a root `package.json`: workflows\n * are written at the git root but run inside the project directory\n * (`working-directory`), keeping the project's own package manager.\n */\nexport const resolveGithubWorkflowsContext = async (\n rootDir: string,\n projectPackageManager: PackageManager\n): Promise<GithubWorkflowsContext> => {\n const projectRootDir = resolve(rootDir);\n\n let repositoryRootDir = projectRootDir;\n try {\n const gitRootDir = await getGitRootDir(projectRootDir);\n if (gitRootDir) repositoryRootDir = resolve(gitRootDir);\n } catch {\n // Not a git repository — keep the project root as the workflow root.\n }\n\n // Discover every Intlayer project of the repository. `listProjects` scans\n // for configuration files, so a fresh project whose config is created later\n // in the init flow may not be counted yet — the workspace manifest check\n // below still catches that monorepo case.\n let repositoryProjectCount = 0;\n try {\n const { projectsPath } = await listProjects({\n baseDir: repositoryRootDir,\n });\n repositoryProjectCount = projectsPath.length;\n } catch {\n repositoryProjectCount = 0;\n }\n\n const isNestedProject = repositoryRootDir !== projectRootDir;\n const isWorkspaceRepository = hasWorkspaceManifest(repositoryRootDir);\n const hasRootPackageJson = existsSync(\n join(repositoryRootDir, 'package.json')\n );\n\n if (!isNestedProject) {\n return {\n workflowsRootDir: projectRootDir,\n packageManager: projectPackageManager,\n options: {\n isMonorepo: isWorkspaceRepository || repositoryProjectCount > 1,\n },\n };\n }\n\n if (hasRootPackageJson) {\n return {\n workflowsRootDir: repositoryRootDir,\n packageManager: detectPackageManager(repositoryRootDir),\n options: { isMonorepo: true },\n };\n }\n\n // Nested project without a root manifest: install and run everything inside\n // the project directory itself.\n return {\n workflowsRootDir: repositoryRootDir,\n packageManager: projectPackageManager,\n options: {\n workingDirectory: relative(repositoryRootDir, projectRootDir)\n .split(sep)\n .join('/'),\n },\n };\n};\n\n/**\n * Package-manager-specific snippets used to assemble the CI workflows.\n * - `setupSteps`: YAML steps (already indented for the `steps:` list) that\n * install the package manager and Node.js, including dependency caching.\n * - `installCommand`: command that installs the project dependencies.\n * - `execCommand`: prefix used to run the locally installed `intlayer` binary.\n */\ntype PackageManagerCIConfig = {\n setupSteps: string;\n installCommand: string;\n execCommand: string;\n};\n\n/** Dependency lock file of each package manager, used for cache invalidation. */\nconst LOCK_FILE_BY_PACKAGE_MANAGER: Record<PackageManager, string> = {\n bun: 'bun.lock',\n pnpm: 'pnpm-lock.yaml',\n yarn: 'yarn.lock',\n npm: 'package-lock.json',\n};\n\n/**\n * Returns the package-manager-specific steps and commands used to build the\n * GitHub Actions workflows. Each package manager needs a slightly different\n * setup action and a different way to run the local `intlayer` binary.\n *\n * `workingDirectory` is the repository-relative directory holding the lock\n * file when the project is nested: `actions/setup-node` looks for the lock\n * file at the repository root by default and fails the cache setup otherwise.\n */\nconst getPackageManagerCIConfig = (\n packageManager: PackageManager,\n workingDirectory?: string\n): PackageManagerCIConfig => {\n const setupNodeStep = (cache: PackageManager | undefined): string => {\n const cacheDependencyPath =\n cache && workingDirectory\n ? `\\n cache-dependency-path: ${workingDirectory}/${LOCK_FILE_BY_PACKAGE_MANAGER[cache]}`\n : '';\n\n return ` - name: 🟢 Setup Node.js\n uses: actions/setup-node@v4\n with:\n node-version: 20${cache ? `\\n cache: ${cache}` : ''}${cacheDependencyPath}`;\n };\n\n switch (packageManager) {\n case 'bun':\n return {\n setupSteps: ` - name: 🥟 Setup Bun\n uses: oven-sh/setup-bun@v2`,\n installCommand: 'bun install --frozen-lockfile',\n execCommand: 'bunx intlayer',\n };\n case 'pnpm':\n return {\n setupSteps: ` - name: 📦 Setup pnpm\n uses: pnpm/action-setup@v4\n${setupNodeStep('pnpm')}`,\n installCommand: 'pnpm install --frozen-lockfile',\n execCommand: 'pnpm exec intlayer',\n };\n case 'yarn':\n return {\n setupSteps: setupNodeStep('yarn'),\n installCommand: 'yarn install --frozen-lockfile',\n execCommand: 'yarn intlayer',\n };\n case 'npm':\n return {\n setupSteps: setupNodeStep('npm'),\n installCommand: 'npm ci',\n execCommand: 'npx intlayer',\n };\n }\n};\n\n/**\n * Renders the `defaults.run.working-directory` block for jobs that must run\n * inside a nested project directory. Empty when the commands run at the\n * repository root.\n */\nconst getWorkingDirectoryBlock = (workingDirectory?: string): string =>\n workingDirectory\n ? `\n defaults:\n run:\n working-directory: ${workingDirectory}`\n : '';\n\n/**\n * Renders the env comment + optional wiring for per-project credentials in a\n * monorepo. With `--ci`, the CLI matches each entry of the\n * `INTLAYER_PROJECT_CREDENTIALS` JSON map to a discovered project path and\n * injects its access keys before running the command in that project.\n */\nconst getMonorepoCredentialsBlock = (isMonorepo?: boolean): string =>\n isMonorepo\n ? `\n #\n # Monorepo — per-project CMS credentials, as a JSON map of project path\n # (relative to the repository root) to access keys, e.g.\n # {\"apps/web\":{\"clientId\":\"...\",\"clientSecret\":\"...\"}}:\n # INTLAYER_PROJECT_CREDENTIALS: \\${{ secrets.INTLAYER_PROJECT_CREDENTIALS }}`\n : '';\n\n/**\n * Builds the `intlayer fill` workflow. Runs on pull requests, regenerates the\n * missing translations for the changed dictionaries (`--git-diff`) using AI,\n * then commits the result back to the PR branch.\n *\n * Every intlayer command carries `--ci`: the CLI discovers the Intlayer\n * project(s) of the repository and runs the command inside each of them, so\n * the same workflow works for a single project and for a monorepo.\n *\n * AI access is required for `fill`. The workflow exposes both options:\n * - a provider API key (`AI_API_KEY` secret), forwarded via CLI flags, or\n * - Intlayer CMS access keys (`INTLAYER_CLIENT_ID` / `INTLAYER_CLIENT_SECRET`).\n */\nconst generateFillWorkflow = (\n packageManager: PackageManager,\n options: GithubWorkflowsOptions\n): string => {\n const { setupSteps, installCommand, execCommand } = getPackageManagerCIConfig(\n packageManager,\n options.workingDirectory\n );\n\n return `name: Intlayer Fill\n# Auto-fill missing translations on every pull request.\non:\n pull_request:\n branches:\n - main\n\npermissions:\n contents: write\n pull-requests: write\n\nconcurrency:\n group: intlayer-fill-\\${{ github.ref }}\n cancel-in-progress: true\n\njobs:\n fill:\n runs-on: ubuntu-latest${getWorkingDirectoryBlock(options.workingDirectory)}\n env:\n # AI access is required to generate translations.\n # Add the secret in: Settings → Secrets and variables → Actions.\n #\n # Option 1 — Use your own AI provider key (forwarded below via CLI flags):\n AI_PROVIDER: openai\n AI_MODEL: gpt-5-mini\n AI_API_KEY: \\${{ secrets.AI_API_KEY }}\n #\n # Option 2 — Use Intlayer CMS access keys instead of your own AI key.\n # Wire them in your intlayer.config and uncomment the lines below:\n # INTLAYER_CLIENT_ID: \\${{ secrets.INTLAYER_CLIENT_ID }}\n # INTLAYER_CLIENT_SECRET: \\${{ secrets.INTLAYER_CLIENT_SECRET }}${getMonorepoCredentialsBlock(options.isMonorepo)}\n steps:\n - name: ⬇️ Checkout repository\n uses: actions/checkout@v4\n with:\n persist-credentials: true # Keep credentials to push back to the PR\n fetch-depth: 0 # Full history so --git-diff can compare refs\n${setupSteps}\n - name: 📦 Install dependencies\n run: ${installCommand}\n - name: ⚙️ Build dictionaries\n run: ${execCommand} build --ci\n - name: 🤖 Fill missing translations\n # Skip when no AI credentials are configured, so the workflow stays green\n # until an \\`AI_API_KEY\\` (or Intlayer CMS access keys) secret is added.\n if: \\${{ env.AI_API_KEY != '' || env.INTLAYER_CLIENT_ID != '' }}\n run: ${execCommand} fill --ci --git-diff --mode complete --provider $AI_PROVIDER --model $AI_MODEL --api-key $AI_API_KEY\n - name: 📤 Commit and push changes\n run: |\n git config --local user.email \"github-actions[bot]@users.noreply.github.com\"\n git config --local user.name \"github-actions[bot]\"\n if [ -n \"$(git status --porcelain)\" ]; then\n git add .\n git commit -m \"chore(intlayer): fill missing translations [skip ci]\"\n git push origin HEAD:\\${{ github.head_ref }}\n else\n echo \"No missing translations to fill.\"\n fi\n`;\n};\n\n/**\n * Builds the `intlayer test` workflow. Runs on pull requests and fails the\n * check when required locales are missing translations. No AI access needed.\n */\nconst generateTestWorkflow = (\n packageManager: PackageManager,\n options: GithubWorkflowsOptions\n): string => {\n const { setupSteps, installCommand, execCommand } = getPackageManagerCIConfig(\n packageManager,\n options.workingDirectory\n );\n\n return `name: Intlayer Test\n# Fail the pull request when required locales are missing translations.\non:\n pull_request:\n branches:\n - main\n\nconcurrency:\n group: intlayer-test-\\${{ github.ref }}\n cancel-in-progress: true\n\njobs:\n test:\n runs-on: ubuntu-latest${getWorkingDirectoryBlock(options.workingDirectory)}\n steps:\n - name: ⬇️ Checkout repository\n uses: actions/checkout@v4\n${setupSteps}\n - name: 📦 Install dependencies\n run: ${installCommand}\n - name: ⚙️ Build dictionaries\n run: ${execCommand} build --ci\n - name: 🧪 Test for missing translations\n run: ${execCommand} test --ci\n`;\n};\n\n/** Workflow file path constants, relative to the project root. */\nexport const GITHUB_FILL_WORKFLOW_PATH = '.github/workflows/intlayer-fill.yml';\nexport const GITHUB_TEST_WORKFLOW_PATH = '.github/workflows/intlayer-test.yml';\n\n/**\n * Returns the two Intlayer GitHub Actions workflows (`fill` and `test`),\n * generated with commands matching the detected package manager and the\n * repository layout (see {@link GithubWorkflowsOptions} for the monorepo and\n * nested-project variants).\n */\nexport const getGithubWorkflows = (\n packageManager: PackageManager,\n options: GithubWorkflowsOptions = {}\n): GithubWorkflowFile[] => [\n {\n filePath: GITHUB_FILL_WORKFLOW_PATH,\n content: generateFillWorkflow(packageManager, options),\n },\n {\n filePath: GITHUB_TEST_WORKFLOW_PATH,\n content: generateTestWorkflow(packageManager, options),\n },\n];\n"],"mappings":"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"}
|
|
@@ -69,8 +69,7 @@ const getContentWatcherOwner = async (configuration) => {
|
|
|
69
69
|
if (owner.pid && getIsProcessAlive(owner.pid)) return {
|
|
70
70
|
pid: owner.pid,
|
|
71
71
|
source: owner.source ?? "bundler",
|
|
72
|
-
label: owner.label ?? "Intlayer"
|
|
73
|
-
since: owner.since
|
|
72
|
+
label: owner.label ?? "Intlayer"
|
|
74
73
|
};
|
|
75
74
|
try {
|
|
76
75
|
await rm(lockFilePath, { force: true });
|
|
@@ -145,16 +144,16 @@ let hasReportedRedundantWatcher = false;
|
|
|
145
144
|
* per command, and callers may re-enter this on a retry loop.
|
|
146
145
|
*
|
|
147
146
|
* @param configuration - The resolved Intlayer configuration.
|
|
148
|
-
* @param report - Who is watching
|
|
147
|
+
* @param report - Who is watching in parallel with whom.
|
|
149
148
|
*/
|
|
150
|
-
const reportRedundantContentWatcher = (configuration, { cliLabel, bundlerLabel
|
|
149
|
+
const reportRedundantContentWatcher = (configuration, { cliLabel, bundlerLabel }) => {
|
|
151
150
|
if (hasReportedRedundantWatcher) return;
|
|
152
151
|
hasReportedRedundantWatcher = true;
|
|
153
152
|
getAppLogger(configuration)([
|
|
154
153
|
colorize(cliLabel, ANSIColors.BLUE, ANSIColors.BEIGE),
|
|
155
154
|
"is watching your content declarations in parallel with",
|
|
156
155
|
`${colorize(bundlerLabel, ANSIColors.BLUE, ANSIColors.BEIGE)},`,
|
|
157
|
-
|
|
156
|
+
"which watches them on its own.",
|
|
158
157
|
"The parallel watch command is no longer needed — running your dev server on its own is enough."
|
|
159
158
|
], { level: "warn" });
|
|
160
159
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"contentWatcherLock.mjs","names":[],"sources":["../../../src/utils/contentWatcherLock.ts"],"sourcesContent":["import { rmSync } from 'node:fs';\nimport { mkdir, readFile, rm, writeFile } from 'node:fs/promises';\nimport { dirname, join } from 'node:path';\nimport * as ANSIColors from '@intlayer/config/colors';\nimport { colorize, getAppLogger } from '@intlayer/config/logger';\nimport type { IntlayerConfig } from '@intlayer/types/config';\n\n/**\n * What kind of process is watching the content declarations.\n *\n * - `cli` — the `intlayer watch` / `intlayer build --watch` command.\n * - `bundler` — an Intlayer bundler plugin (e.g. `withIntlayer` on Next.js).\n */\nexport type ContentWatcherSource = 'cli' | 'bundler';\n\nexport type ContentWatcherOwner = {\n /** PID of the process holding the watcher. */\n pid: number;\n source: ContentWatcherSource;\n /**\n * How the owner should be named in a message, e.g. `intlayer watch` or\n * `next-intlayer`.\n */\n label: string;\n /**\n * Release from which a `bundler` owner watches the content declarations on\n * its own, so a CLI watcher finding it can name the version that made the\n * parallel command redundant without keeping a table of its own.\n */\n since?: string;\n};\n\n/**\n * Set on every process spawned by `intlayer watch --with` (and\n * `intlayer build --watch --with`), so a bundler plugin running as that child\n * knows a CLI watcher is already covering the project — without having to\n * depend on the two processes reaching the lock file in a given order.\n *\n * Holds the label of the command that set it.\n */\nexport const CLI_CONTENT_WATCHER_ENV_VAR = 'INTLAYER_CLI_CONTENT_WATCHER';\n\n/** Name of the file materialising the ownership of the content watcher. */\nconst CONTENT_WATCHER_LOCK_FILE_NAME = 'intlayer-content-watcher.lock';\n\n/** How many times taking the lock may follow up on a reclaimed stale one. */\nconst ACQUIRE_ATTEMPTS = 3;\n\n/** Lock owned by this process, released on exit. */\nlet ownedLockFilePath: string | undefined;\n\n// The one place that has to stay synchronous: Node runs `exit` listeners to\n// completion without an event loop turn, so a promise scheduled here would\n// never settle and the lock would outlive the process — stalling the next dev\n// session until its PID check reclaims it.\nprocess.on('exit', () => {\n if (!ownedLockFilePath) return;\n\n try {\n rmSync(ownedLockFilePath, { force: true });\n } catch {}\n});\n\n/**\n * Path of the lock coordinating every content watcher of one project.\n *\n * Deliberately at the root of `.intlayer` rather than in its `cache`\n * subdirectory: `cleanOutputDir` wipes every subdirectory of `.intlayer`, so a\n * lock kept in `cache` would be erased by any `prepareIntlayer` that cleans —\n * after which a bundler plugin would see no owner and start a second watcher\n * next to the `intlayer watch` still running. The root itself is never removed.\n *\n * @param configuration - The resolved Intlayer configuration.\n */\nexport const getContentWatcherLockFilePath = (\n configuration: IntlayerConfig\n): string => join(configuration.system.tempDir, CONTENT_WATCHER_LOCK_FILE_NAME);\n\n/**\n * Whether a PID belongs to a process that is still running.\n */\nconst getIsProcessAlive = (pid: number): boolean => {\n if (pid === process.pid) return true;\n\n try {\n // Signal 0 checks for existence without delivering a signal.\n process.kill(pid, 0);\n return true;\n } catch (error) {\n // EPERM means the process exists but belongs to another user.\n return (error as NodeJS.ErrnoException).code === 'EPERM';\n }\n};\n\n/**\n * Reads the lock, removing it when it describes a process that has since died.\n *\n * An unreadable or malformed lock is treated as abandoned: a corrupted file\n * must never block the watcher for a whole dev session.\n *\n * @returns The live owner, or `null` when nothing is watching.\n */\nexport const getContentWatcherOwner = async (\n configuration: IntlayerConfig\n): Promise<ContentWatcherOwner | null> => {\n const lockFilePath = getContentWatcherLockFilePath(configuration);\n\n let owner: Partial<ContentWatcherOwner>;\n\n try {\n owner = JSON.parse(await readFile(lockFilePath, 'utf8'));\n } catch {\n return null;\n }\n\n if (owner.pid && getIsProcessAlive(owner.pid)) {\n return {\n pid: owner.pid,\n source: owner.source ?? 'bundler',\n label: owner.label ?? 'Intlayer',\n since: owner.since,\n };\n }\n\n try {\n await rm(lockFilePath, { force: true });\n } catch {}\n\n return null;\n};\n\n/**\n * Tries to become the process that watches this project's content\n * declarations.\n *\n * Creating the file with `wx` is atomic, so exactly one process wins even when\n * several try at the same moment — which is the normal case on Next.js, where\n * `next.config.*` is evaluated in more than one process.\n *\n * @param configuration - The resolved Intlayer configuration.\n * @param owner - How this process should describe itself to the others.\n * @returns `true` when this process now owns the watcher.\n */\nexport const acquireContentWatcherLock = async (\n configuration: IntlayerConfig,\n owner: Omit<ContentWatcherOwner, 'pid'>\n): Promise<boolean> => {\n const lockFilePath = getContentWatcherLockFilePath(configuration);\n const data = JSON.stringify({\n pid: process.pid,\n ...owner,\n } satisfies ContentWatcherOwner);\n\n // Bounded rather than recursive: the retry only exists to follow up on a lock\n // `getContentWatcherOwner` has just reclaimed, and two processes reclaiming\n // and re-taking it in turn must not spin forever.\n for (let attempt = 0; attempt < ACQUIRE_ATTEMPTS; attempt++) {\n try {\n await mkdir(dirname(lockFilePath), { recursive: true });\n await writeFile(lockFilePath, data, { flag: 'wx' });\n\n ownedLockFilePath = lockFilePath;\n\n return true;\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== 'EEXIST') return false;\n\n // A lock left behind by a dead process is reclaimed by\n // `getContentWatcherOwner`, leaving the next attempt free to take it.\n if (await getContentWatcherOwner(configuration)) return false;\n }\n }\n\n return false;\n};\n\n/**\n * The CLI watcher command this process was spawned by, or `null` when it was\n * not started through `intlayer watch --with`.\n */\nexport const getCliContentWatcherLabel = (): string | null =>\n process.env[CLI_CONTENT_WATCHER_ENV_VAR] || null;\n\n/**\n * Registers a CLI command as the content watcher of this project.\n *\n * Call it *before* spawning a `--with` child: taking the lock first means the\n * bundler plugin inside that child never races the command for it, and the\n * environment marker makes the child's decision independent of the filesystem\n * altogether.\n *\n * @param configuration - The resolved Intlayer configuration.\n * @param label - How the command should be named in a message.\n * @returns The bundler already watching this project, or `null` when the\n * command took the watcher for itself. A CLI watcher never stands down on that\n * answer — watching is what it was asked to do — but reporting it lets the user\n * know the parallel command has become redundant.\n */\nexport const claimCliContentWatcher = async (\n configuration: IntlayerConfig,\n label: string\n): Promise<ContentWatcherOwner | null> => {\n // Set before the first `await`: a `--with` child spawned while the lock is\n // still being written must already see the marker in its environment.\n process.env[CLI_CONTENT_WATCHER_ENV_VAR] = label;\n\n const hasAcquiredLock = await acquireContentWatcherLock(configuration, {\n source: 'cli',\n label,\n });\n\n if (hasAcquiredLock) return null;\n\n const owner = await getContentWatcherOwner(configuration);\n\n return owner?.source === 'bundler' ? owner : null;\n};\n\n/** Keeps the redundant-watcher notice to one per process. */\nlet hasReportedRedundantWatcher = false;\n\ntype RedundantWatcherReport = {\n /** Name of the CLI command watching, e.g. `intlayer watch`. */\n cliLabel: string;\n /** Name of the bundler integration watching, e.g. `next-intlayer`. */\n bundlerLabel: string;\n /**\n * Release from which the bundler integration watches on its own. Omitted when\n * the integration did not report one.\n */\n since?: string;\n};\n\n/**\n * Reports a CLI watcher running alongside a bundler integration that watches\n * on its own, and points at the parallel command as the part to drop.\n *\n * Emitted at most once per process: a bundler config is evaluated several times\n * per command, and callers may re-enter this on a retry loop.\n *\n * @param configuration - The resolved Intlayer configuration.\n * @param report - Who is watching, and from which release it became redundant.\n */\nexport const reportRedundantContentWatcher = (\n configuration: IntlayerConfig,\n { cliLabel, bundlerLabel, since }: RedundantWatcherReport\n): void => {\n if (hasReportedRedundantWatcher) return;\n hasReportedRedundantWatcher = true;\n\n getAppLogger(configuration)(\n [\n colorize(cliLabel, ANSIColors.BLUE, ANSIColors.BEIGE),\n 'is watching your content declarations in parallel with',\n `${colorize(bundlerLabel, ANSIColors.BLUE, ANSIColors.BEIGE)},`,\n since\n ? `which watches them on its own since ${colorize(`${bundlerLabel}@${since}`, ANSIColors.GREY_LIGHT, ANSIColors.BEIGE)}.`\n : 'which watches them on its own.',\n 'The parallel watch command is no longer needed — running your dev server on its own is enough.',\n ],\n { level: 'warn' }\n );\n};\n"],"mappings":";;;;;;;;;;;;;;;AAwCA,MAAa,8BAA8B;;AAG3C,MAAM,iCAAiC;;AAGvC,MAAM,mBAAmB;;AAGzB,IAAI;AAMJ,QAAQ,GAAG,cAAc;CACvB,IAAI,CAAC,mBAAmB;CAExB,IAAI;EACF,OAAO,mBAAmB,EAAE,OAAO,KAAK,CAAC;CAC3C,QAAQ,CAAC;AACX,CAAC;;;;;;;;;;;;AAaD,MAAa,iCACX,kBACW,KAAK,cAAc,OAAO,SAAS,8BAA8B;;;;AAK9E,MAAM,qBAAqB,QAAyB;CAClD,IAAI,QAAQ,QAAQ,KAAK,OAAO;CAEhC,IAAI;EAEF,QAAQ,KAAK,KAAK,CAAC;EACnB,OAAO;CACT,SAAS,OAAO;EAEd,OAAQ,MAAgC,SAAS;CACnD;AACF;;;;;;;;;AAUA,MAAa,yBAAyB,OACpC,kBACwC;CACxC,MAAM,eAAe,8BAA8B,aAAa;CAEhE,IAAI;CAEJ,IAAI;EACF,QAAQ,KAAK,MAAM,MAAM,SAAS,cAAc,MAAM,CAAC;CACzD,QAAQ;EACN,OAAO;CACT;CAEA,IAAI,MAAM,OAAO,kBAAkB,MAAM,GAAG,GAC1C,OAAO;EACL,KAAK,MAAM;EACX,QAAQ,MAAM,UAAU;EACxB,OAAO,MAAM,SAAS;EACtB,OAAO,MAAM;CACf;CAGF,IAAI;EACF,MAAM,GAAG,cAAc,EAAE,OAAO,KAAK,CAAC;CACxC,QAAQ,CAAC;CAET,OAAO;AACT;;;;;;;;;;;;;AAcA,MAAa,4BAA4B,OACvC,eACA,UACqB;CACrB,MAAM,eAAe,8BAA8B,aAAa;CAChE,MAAM,OAAO,KAAK,UAAU;EAC1B,KAAK,QAAQ;EACb,GAAG;CACL,CAA+B;CAK/B,KAAK,IAAI,UAAU,GAAG,UAAU,kBAAkB,WAChD,IAAI;EACF,MAAM,MAAM,QAAQ,YAAY,GAAG,EAAE,WAAW,KAAK,CAAC;EACtD,MAAM,UAAU,cAAc,MAAM,EAAE,MAAM,KAAK,CAAC;EAElD,oBAAoB;EAEpB,OAAO;CACT,SAAS,OAAO;EACd,IAAK,MAAgC,SAAS,UAAU,OAAO;EAI/D,IAAI,MAAM,uBAAuB,aAAa,GAAG,OAAO;CAC1D;CAGF,OAAO;AACT;;;;;AAMA,MAAa,kCACX,QAAQ,uCAAoC;;;;;;;;;;;;;;;;AAiB9C,MAAa,yBAAyB,OACpC,eACA,UACwC;CAGxC,QAAQ,IAAI,+BAA+B;CAO3C,IAAI,MAL0B,0BAA0B,eAAe;EACrE,QAAQ;EACR;CACF,CAAC,GAEoB,OAAO;CAE5B,MAAM,QAAQ,MAAM,uBAAuB,aAAa;CAExD,OAAO,OAAO,WAAW,YAAY,QAAQ;AAC/C;;AAGA,IAAI,8BAA8B;;;;;;;;;;;AAwBlC,MAAa,iCACX,eACA,EAAE,UAAU,cAAc,YACjB;CACT,IAAI,6BAA6B;CACjC,8BAA8B;CAE9B,aAAa,aAAa,CAAC,CACzB;EACE,SAAS,UAAU,WAAW,MAAM,WAAW,KAAK;EACpD;EACA,GAAG,SAAS,cAAc,WAAW,MAAM,WAAW,KAAK,EAAE;EAC7D,QACI,uCAAuC,SAAS,GAAG,aAAa,GAAG,SAAS,WAAW,YAAY,WAAW,KAAK,EAAE,KACrH;EACJ;CACF,GACA,EAAE,OAAO,OAAO,CAClB;AACF"}
|
|
1
|
+
{"version":3,"file":"contentWatcherLock.mjs","names":[],"sources":["../../../src/utils/contentWatcherLock.ts"],"sourcesContent":["import { rmSync } from 'node:fs';\nimport { mkdir, readFile, rm, writeFile } from 'node:fs/promises';\nimport { dirname, join } from 'node:path';\nimport * as ANSIColors from '@intlayer/config/colors';\nimport { colorize, getAppLogger } from '@intlayer/config/logger';\nimport type { IntlayerConfig } from '@intlayer/types/config';\n\n/**\n * What kind of process is watching the content declarations.\n *\n * - `cli` — the `intlayer watch` / `intlayer build --watch` command.\n * - `bundler` — an Intlayer bundler plugin (e.g. `withIntlayer` on Next.js).\n */\nexport type ContentWatcherSource = 'cli' | 'bundler';\n\nexport type ContentWatcherOwner = {\n /** PID of the process holding the watcher. */\n pid: number;\n source: ContentWatcherSource;\n /**\n * How the owner should be named in a message, e.g. `intlayer watch` or\n * `next-intlayer`.\n */\n label: string;\n};\n\n/**\n * Set on every process spawned by `intlayer watch --with` (and\n * `intlayer build --watch --with`), so a bundler plugin running as that child\n * knows a CLI watcher is already covering the project — without having to\n * depend on the two processes reaching the lock file in a given order.\n *\n * Holds the label of the command that set it.\n */\nexport const CLI_CONTENT_WATCHER_ENV_VAR = 'INTLAYER_CLI_CONTENT_WATCHER';\n\n/** Name of the file materialising the ownership of the content watcher. */\nconst CONTENT_WATCHER_LOCK_FILE_NAME = 'intlayer-content-watcher.lock';\n\n/** How many times taking the lock may follow up on a reclaimed stale one. */\nconst ACQUIRE_ATTEMPTS = 3;\n\n/** Lock owned by this process, released on exit. */\nlet ownedLockFilePath: string | undefined;\n\n// The one place that has to stay synchronous: Node runs `exit` listeners to\n// completion without an event loop turn, so a promise scheduled here would\n// never settle and the lock would outlive the process — stalling the next dev\n// session until its PID check reclaims it.\nprocess.on('exit', () => {\n if (!ownedLockFilePath) return;\n\n try {\n rmSync(ownedLockFilePath, { force: true });\n } catch {}\n});\n\n/**\n * Path of the lock coordinating every content watcher of one project.\n *\n * Deliberately at the root of `.intlayer` rather than in its `cache`\n * subdirectory: `cleanOutputDir` wipes every subdirectory of `.intlayer`, so a\n * lock kept in `cache` would be erased by any `prepareIntlayer` that cleans —\n * after which a bundler plugin would see no owner and start a second watcher\n * next to the `intlayer watch` still running. The root itself is never removed.\n *\n * @param configuration - The resolved Intlayer configuration.\n */\nexport const getContentWatcherLockFilePath = (\n configuration: IntlayerConfig\n): string => join(configuration.system.tempDir, CONTENT_WATCHER_LOCK_FILE_NAME);\n\n/**\n * Whether a PID belongs to a process that is still running.\n */\nconst getIsProcessAlive = (pid: number): boolean => {\n if (pid === process.pid) return true;\n\n try {\n // Signal 0 checks for existence without delivering a signal.\n process.kill(pid, 0);\n return true;\n } catch (error) {\n // EPERM means the process exists but belongs to another user.\n return (error as NodeJS.ErrnoException).code === 'EPERM';\n }\n};\n\n/**\n * Reads the lock, removing it when it describes a process that has since died.\n *\n * An unreadable or malformed lock is treated as abandoned: a corrupted file\n * must never block the watcher for a whole dev session.\n *\n * @returns The live owner, or `null` when nothing is watching.\n */\nexport const getContentWatcherOwner = async (\n configuration: IntlayerConfig\n): Promise<ContentWatcherOwner | null> => {\n const lockFilePath = getContentWatcherLockFilePath(configuration);\n\n let owner: Partial<ContentWatcherOwner>;\n\n try {\n owner = JSON.parse(await readFile(lockFilePath, 'utf8'));\n } catch {\n return null;\n }\n\n if (owner.pid && getIsProcessAlive(owner.pid)) {\n return {\n pid: owner.pid,\n source: owner.source ?? 'bundler',\n label: owner.label ?? 'Intlayer',\n };\n }\n\n try {\n await rm(lockFilePath, { force: true });\n } catch {}\n\n return null;\n};\n\n/**\n * Tries to become the process that watches this project's content\n * declarations.\n *\n * Creating the file with `wx` is atomic, so exactly one process wins even when\n * several try at the same moment — which is the normal case on Next.js, where\n * `next.config.*` is evaluated in more than one process.\n *\n * @param configuration - The resolved Intlayer configuration.\n * @param owner - How this process should describe itself to the others.\n * @returns `true` when this process now owns the watcher.\n */\nexport const acquireContentWatcherLock = async (\n configuration: IntlayerConfig,\n owner: Omit<ContentWatcherOwner, 'pid'>\n): Promise<boolean> => {\n const lockFilePath = getContentWatcherLockFilePath(configuration);\n const data = JSON.stringify({\n pid: process.pid,\n ...owner,\n } satisfies ContentWatcherOwner);\n\n // Bounded rather than recursive: the retry only exists to follow up on a lock\n // `getContentWatcherOwner` has just reclaimed, and two processes reclaiming\n // and re-taking it in turn must not spin forever.\n for (let attempt = 0; attempt < ACQUIRE_ATTEMPTS; attempt++) {\n try {\n await mkdir(dirname(lockFilePath), { recursive: true });\n await writeFile(lockFilePath, data, { flag: 'wx' });\n\n ownedLockFilePath = lockFilePath;\n\n return true;\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== 'EEXIST') return false;\n\n // A lock left behind by a dead process is reclaimed by\n // `getContentWatcherOwner`, leaving the next attempt free to take it.\n if (await getContentWatcherOwner(configuration)) return false;\n }\n }\n\n return false;\n};\n\n/**\n * The CLI watcher command this process was spawned by, or `null` when it was\n * not started through `intlayer watch --with`.\n */\nexport const getCliContentWatcherLabel = (): string | null =>\n process.env[CLI_CONTENT_WATCHER_ENV_VAR] || null;\n\n/**\n * Registers a CLI command as the content watcher of this project.\n *\n * Call it *before* spawning a `--with` child: taking the lock first means the\n * bundler plugin inside that child never races the command for it, and the\n * environment marker makes the child's decision independent of the filesystem\n * altogether.\n *\n * @param configuration - The resolved Intlayer configuration.\n * @param label - How the command should be named in a message.\n * @returns The bundler already watching this project, or `null` when the\n * command took the watcher for itself. A CLI watcher never stands down on that\n * answer — watching is what it was asked to do — but reporting it lets the user\n * know the parallel command has become redundant.\n */\nexport const claimCliContentWatcher = async (\n configuration: IntlayerConfig,\n label: string\n): Promise<ContentWatcherOwner | null> => {\n // Set before the first `await`: a `--with` child spawned while the lock is\n // still being written must already see the marker in its environment.\n process.env[CLI_CONTENT_WATCHER_ENV_VAR] = label;\n\n const hasAcquiredLock = await acquireContentWatcherLock(configuration, {\n source: 'cli',\n label,\n });\n\n if (hasAcquiredLock) return null;\n\n const owner = await getContentWatcherOwner(configuration);\n\n return owner?.source === 'bundler' ? owner : null;\n};\n\n/** Keeps the redundant-watcher notice to one per process. */\nlet hasReportedRedundantWatcher = false;\n\ntype RedundantWatcherReport = {\n /** Name of the CLI command watching, e.g. `intlayer watch`. */\n cliLabel: string;\n /** Name of the bundler integration watching, e.g. `next-intlayer`. */\n bundlerLabel: string;\n};\n\n/**\n * Reports a CLI watcher running alongside a bundler integration that watches\n * on its own, and points at the parallel command as the part to drop.\n *\n * Emitted at most once per process: a bundler config is evaluated several times\n * per command, and callers may re-enter this on a retry loop.\n *\n * @param configuration - The resolved Intlayer configuration.\n * @param report - Who is watching in parallel with whom.\n */\nexport const reportRedundantContentWatcher = (\n configuration: IntlayerConfig,\n { cliLabel, bundlerLabel }: RedundantWatcherReport\n): void => {\n if (hasReportedRedundantWatcher) return;\n hasReportedRedundantWatcher = true;\n\n getAppLogger(configuration)(\n [\n colorize(cliLabel, ANSIColors.BLUE, ANSIColors.BEIGE),\n 'is watching your content declarations in parallel with',\n `${colorize(bundlerLabel, ANSIColors.BLUE, ANSIColors.BEIGE)},`,\n 'which watches them on its own.',\n 'The parallel watch command is no longer needed — running your dev server on its own is enough.',\n ],\n { level: 'warn' }\n );\n};\n"],"mappings":";;;;;;;;;;;;;;;AAkCA,MAAa,8BAA8B;;AAG3C,MAAM,iCAAiC;;AAGvC,MAAM,mBAAmB;;AAGzB,IAAI;AAMJ,QAAQ,GAAG,cAAc;CACvB,IAAI,CAAC,mBAAmB;CAExB,IAAI;EACF,OAAO,mBAAmB,EAAE,OAAO,KAAK,CAAC;CAC3C,QAAQ,CAAC;AACX,CAAC;;;;;;;;;;;;AAaD,MAAa,iCACX,kBACW,KAAK,cAAc,OAAO,SAAS,8BAA8B;;;;AAK9E,MAAM,qBAAqB,QAAyB;CAClD,IAAI,QAAQ,QAAQ,KAAK,OAAO;CAEhC,IAAI;EAEF,QAAQ,KAAK,KAAK,CAAC;EACnB,OAAO;CACT,SAAS,OAAO;EAEd,OAAQ,MAAgC,SAAS;CACnD;AACF;;;;;;;;;AAUA,MAAa,yBAAyB,OACpC,kBACwC;CACxC,MAAM,eAAe,8BAA8B,aAAa;CAEhE,IAAI;CAEJ,IAAI;EACF,QAAQ,KAAK,MAAM,MAAM,SAAS,cAAc,MAAM,CAAC;CACzD,QAAQ;EACN,OAAO;CACT;CAEA,IAAI,MAAM,OAAO,kBAAkB,MAAM,GAAG,GAC1C,OAAO;EACL,KAAK,MAAM;EACX,QAAQ,MAAM,UAAU;EACxB,OAAO,MAAM,SAAS;CACxB;CAGF,IAAI;EACF,MAAM,GAAG,cAAc,EAAE,OAAO,KAAK,CAAC;CACxC,QAAQ,CAAC;CAET,OAAO;AACT;;;;;;;;;;;;;AAcA,MAAa,4BAA4B,OACvC,eACA,UACqB;CACrB,MAAM,eAAe,8BAA8B,aAAa;CAChE,MAAM,OAAO,KAAK,UAAU;EAC1B,KAAK,QAAQ;EACb,GAAG;CACL,CAA+B;CAK/B,KAAK,IAAI,UAAU,GAAG,UAAU,kBAAkB,WAChD,IAAI;EACF,MAAM,MAAM,QAAQ,YAAY,GAAG,EAAE,WAAW,KAAK,CAAC;EACtD,MAAM,UAAU,cAAc,MAAM,EAAE,MAAM,KAAK,CAAC;EAElD,oBAAoB;EAEpB,OAAO;CACT,SAAS,OAAO;EACd,IAAK,MAAgC,SAAS,UAAU,OAAO;EAI/D,IAAI,MAAM,uBAAuB,aAAa,GAAG,OAAO;CAC1D;CAGF,OAAO;AACT;;;;;AAMA,MAAa,kCACX,QAAQ,uCAAoC;;;;;;;;;;;;;;;;AAiB9C,MAAa,yBAAyB,OACpC,eACA,UACwC;CAGxC,QAAQ,IAAI,+BAA+B;CAO3C,IAAI,MAL0B,0BAA0B,eAAe;EACrE,QAAQ;EACR;CACF,CAAC,GAEoB,OAAO;CAE5B,MAAM,QAAQ,MAAM,uBAAuB,aAAa;CAExD,OAAO,OAAO,WAAW,YAAY,QAAQ;AAC/C;;AAGA,IAAI,8BAA8B;;;;;;;;;;;AAmBlC,MAAa,iCACX,eACA,EAAE,UAAU,mBACH;CACT,IAAI,6BAA6B;CACjC,8BAA8B;CAE9B,aAAa,aAAa,CAAC,CACzB;EACE,SAAS,UAAU,WAAW,MAAM,WAAW,KAAK;EACpD;EACA,GAAG,SAAS,cAAc,WAAW,MAAM,WAAW,KAAK,EAAE;EAC7D;EACA;CACF,GACA,EAAE,OAAO,OAAO,CAClB;AACF"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { LocalesValues } from "./intlayer/dist/types/index.js";
|
|
1
2
|
import { Dictionary } from "@intlayer/types/dictionary";
|
|
2
3
|
//#region src/formatDictionary.d.ts
|
|
3
4
|
export declare const formatDictionary: (dictionary: Dictionary) => Dictionary;
|
|
@@ -18,7 +19,7 @@ export declare const formatDictionaryOutput: (dictionary: Dictionary, format: Di
|
|
|
18
19
|
tags?: string[];
|
|
19
20
|
item?: number;
|
|
20
21
|
variant?: import("@intlayer/types").DictionaryVariantValue | import("@intlayer/types").DictionaryVariantValue[];
|
|
21
|
-
locale?:
|
|
22
|
+
locale?: LocalesValues;
|
|
22
23
|
contentAutoTransformation?: import("@intlayer/types").ContentAutoTransformation;
|
|
23
24
|
fill?: import("@intlayer/types").Fill;
|
|
24
25
|
filled?: true;
|
|
@@ -45,7 +46,7 @@ export declare const formatDictionariesOutput: (dictionaries: Dictionary[], form
|
|
|
45
46
|
tags?: string[];
|
|
46
47
|
item?: number;
|
|
47
48
|
variant?: import("@intlayer/types").DictionaryVariantValue | import("@intlayer/types").DictionaryVariantValue[];
|
|
48
|
-
locale?:
|
|
49
|
+
locale?: LocalesValues;
|
|
49
50
|
contentAutoTransformation?: import("@intlayer/types").ContentAutoTransformation;
|
|
50
51
|
fill?: import("@intlayer/types").Fill;
|
|
51
52
|
filled?: true;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"formatDictionary.d.ts","names":[],"sources":["../../src/formatDictionary.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"formatDictionary.d.ts","names":[],"sources":["../../src/formatDictionary.ts"],"mappings":";;;qBAYa,mBAAgB,YAAgB,eAAa;qBAoC7C,qBAAkB,cACf,iBACb,QAAQ;qBAEE,yBAAsB,YACrB,YAAU,QACd;;;;;;;;;;;;;;;;;;;;;;;;;;IAmCT;qBAEY,2BAAwB,cACrB,cAAY,QAClB;;;;;;;;;;;;;;;;;;;;;;;;;;IAEoE"}
|
|
@@ -19,9 +19,8 @@ export type ResolveDevScriptParams = {
|
|
|
19
19
|
* project's script must be left alone.
|
|
20
20
|
*
|
|
21
21
|
* A Next.js app is always left alone: `withIntlayer` starts the content watcher
|
|
22
|
-
* itself
|
|
23
|
-
*
|
|
24
|
-
* add a second watcher rebuilding the same dictionaries. This is checked on the
|
|
22
|
+
* itself on both bundlers, so wrapping the dev server in `intlayer watch --with`
|
|
23
|
+
* would only add a second watcher rebuilding the same dictionaries. This is checked on the
|
|
25
24
|
* `next` dependency as well as on the config file, since `next.config.*` is
|
|
26
25
|
* optional, and it takes precedence over the backend list, which a Next.js app
|
|
27
26
|
* with a custom server (or a monorepo root) can match too.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"devScript.d.ts","names":[],"sources":["../../../../src/init/utils/devScript.ts"],"mappings":";;;;;;;qBAMa;YAQD;;EAEV;;EAEA,SAAS;;EAET
|
|
1
|
+
{"version":3,"file":"devScript.d.ts","names":[],"sources":["../../../../src/init/utils/devScript.ts"],"mappings":";;;;;;;qBAMa;YAQD;;EAEV;;EAEA,SAAS;;EAET;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBAgCW,qBAAgB,WAAA,SAAA,mBAI1B"}
|
|
@@ -10,14 +10,13 @@ export type GithubWorkflowFile = {
|
|
|
10
10
|
/** Options controlling how the CI workflows are generated. */
|
|
11
11
|
export type GithubWorkflowsOptions = {
|
|
12
12
|
/**
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* repository
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* monorepos where a single workflow must cover several projects.
|
|
13
|
+
* The repository hosts several Intlayer projects. Every scaffolded command
|
|
14
|
+
* always carries `--ci` (the CLI discovers each Intlayer project of the
|
|
15
|
+
* repository and runs the command inside it), so this only adds the
|
|
16
|
+
* `INTLAYER_PROJECT_CREDENTIALS` hint to the workflow env: a JSON map of
|
|
17
|
+
* project path to CMS access keys injected per project by the CLI.
|
|
19
18
|
*/
|
|
20
|
-
|
|
19
|
+
isMonorepo?: boolean;
|
|
21
20
|
/**
|
|
22
21
|
* Repository-relative directory (posix separators) the workflow commands run
|
|
23
22
|
* in. Set when the Intlayer project lives in a subdirectory of the
|
|
@@ -49,13 +48,12 @@ export type GithubWorkflowsContext = {
|
|
|
49
48
|
*
|
|
50
49
|
* - Project at the repository root: workflows are written in place. When the
|
|
51
50
|
* repository is a monorepo (workspace manifest, or several Intlayer projects
|
|
52
|
-
* discovered by `listProjects`), the
|
|
53
|
-
* every project is covered.
|
|
51
|
+
* discovered by `listProjects`), the per-project credentials hint is added.
|
|
54
52
|
* - Project nested in a repository whose root has a `package.json`
|
|
55
53
|
* (workspace-managed monorepo): workflows are written at the git root —
|
|
56
54
|
* GitHub ignores `.github/workflows` in subdirectories — dependencies are
|
|
57
|
-
* installed at the root with the root's package manager, and the
|
|
58
|
-
*
|
|
55
|
+
* installed at the root with the root's package manager, and the per-project
|
|
56
|
+
* credentials hint is added.
|
|
59
57
|
* - Project nested in a repository without a root `package.json`: workflows
|
|
60
58
|
* are written at the git root but run inside the project directory
|
|
61
59
|
* (`working-directory`), keeping the project's own package manager.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"githubActions.d.ts","names":[],"sources":["../../../../src/init/utils/githubActions.ts"],"mappings":";;;YAMY;;EAEV;;EAEA;;;YAIU
|
|
1
|
+
{"version":3,"file":"githubActions.d.ts","names":[],"sources":["../../../../src/init/utils/githubActions.ts"],"mappings":";;;YAMY;;EAEV;;EAEA;;;YAIU;;;;;;;;EAQV;;;;;;;EAOA;;;;;;YAOU;;;;;;EAMV;;EAEA,gBAAgB;;EAEhB,SAAS;;;;;;;;;;;;;;;;;;;qBAwCE,gCAA6B,iBACzB,uBACQ,mBACtB,QAAQ;;qBAoSE;qBACA;;;;;;;qBAQA,qBAAkB,gBACb,gBAAc,UACrB,2BACR"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { IntlayerConfig } from "@intlayer/types/config";
|
|
2
|
+
import { Dictionary } from "@intlayer/types/dictionary";
|
|
3
|
+
import "@intlayer/types/allLocales";
|
|
4
|
+
import { LocalesValues as LocalesValues$1 } from "@intlayer/types/module_augmentation";
|
|
5
|
+
import "@intlayer/types/locales";
|
|
6
|
+
import "@intlayer/config/built";
|
|
7
|
+
import "@intlayer/core/file";
|
|
8
|
+
import "@intlayer/core/formatters";
|
|
9
|
+
import "@intlayer/core/interpreter";
|
|
10
|
+
import "@intlayer/core/localization";
|
|
11
|
+
import "@intlayer/core/markdown";
|
|
12
|
+
import "@intlayer/core/transpiler";
|
|
13
|
+
import "@intlayer/core/utils";
|
|
14
|
+
export type { LocalesValues$1 as LocalesValues };
|
|
@@ -8,7 +8,7 @@ import { IntlayerConfig } from "@intlayer/types/config";
|
|
|
8
8
|
export declare const listDictionaries: (configuration: IntlayerConfig) => Promise<string[]>;
|
|
9
9
|
export declare const listDictionariesWithStats: (configuration: IntlayerConfig) => Promise<{
|
|
10
10
|
path: string;
|
|
11
|
-
stats: import("
|
|
11
|
+
stats: import("fs").Stats;
|
|
12
12
|
}[]>;
|
|
13
13
|
//#endregion
|
|
14
14
|
//# sourceMappingURL=listDictionariesPath.d.ts.map
|
|
@@ -16,12 +16,6 @@ export type ContentWatcherOwner = {
|
|
|
16
16
|
* `next-intlayer`.
|
|
17
17
|
*/
|
|
18
18
|
label: string;
|
|
19
|
-
/**
|
|
20
|
-
* Release from which a `bundler` owner watches the content declarations on
|
|
21
|
-
* its own, so a CLI watcher finding it can name the version that made the
|
|
22
|
-
* parallel command redundant without keeping a table of its own.
|
|
23
|
-
*/
|
|
24
|
-
since?: string;
|
|
25
19
|
};
|
|
26
20
|
/**
|
|
27
21
|
* Set on every process spawned by `intlayer watch --with` (and
|
|
@@ -92,11 +86,6 @@ type RedundantWatcherReport = {
|
|
|
92
86
|
cliLabel: string;
|
|
93
87
|
/** Name of the bundler integration watching, e.g. `next-intlayer`. */
|
|
94
88
|
bundlerLabel: string;
|
|
95
|
-
/**
|
|
96
|
-
* Release from which the bundler integration watches on its own. Omitted when
|
|
97
|
-
* the integration did not report one.
|
|
98
|
-
*/
|
|
99
|
-
since?: string;
|
|
100
89
|
};
|
|
101
90
|
/**
|
|
102
91
|
* Reports a CLI watcher running alongside a bundler integration that watches
|
|
@@ -106,8 +95,8 @@ type RedundantWatcherReport = {
|
|
|
106
95
|
* per command, and callers may re-enter this on a retry loop.
|
|
107
96
|
*
|
|
108
97
|
* @param configuration - The resolved Intlayer configuration.
|
|
109
|
-
* @param report - Who is watching
|
|
98
|
+
* @param report - Who is watching in parallel with whom.
|
|
110
99
|
*/
|
|
111
|
-
export declare const reportRedundantContentWatcher: (configuration: IntlayerConfig, { cliLabel, bundlerLabel
|
|
100
|
+
export declare const reportRedundantContentWatcher: (configuration: IntlayerConfig, { cliLabel, bundlerLabel }: RedundantWatcherReport) => void;
|
|
112
101
|
//#endregion
|
|
113
102
|
//# sourceMappingURL=contentWatcherLock.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"contentWatcherLock.d.ts","names":[],"sources":["../../../src/utils/contentWatcherLock.ts"],"mappings":";;;;;;;;YAaY;YAEA;;EAEV;EACA,QAAQ;;;;;EAKR
|
|
1
|
+
{"version":3,"file":"contentWatcherLock.d.ts","names":[],"sources":["../../../src/utils/contentWatcherLock.ts"],"mappings":";;;;;;;;YAaY;YAEA;;EAEV;EACA,QAAQ;;;;;EAKR;;;;;;;;;;qBAWW;;;;;;;;;;;;qBAkCA,gCAA6B,eACzB;;;;;;;;;qBA2BJ,yBAAsB,eAClB,mBACd,QAAQ;;;;;;;;;;;;;qBAsCE,4BAAyB,eACrB,gBAAc,OACtB,KAAK,gCACX;;;;;qBAkCU;;;;;;;;;;;;;;;;qBAkBA,yBAAsB,eAClB,gBAAc,kBAE5B,QAAQ;KAoBN;;EAEH;;EAEA;;;;;;;;;;;;qBAaW,gCAA6B,eACzB,kBAAc,UAAA,gBACD"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@intlayer/engine",
|
|
3
|
-
"version": "9.5.
|
|
3
|
+
"version": "9.5.2",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Scans and builds Intlayer declaration files into dictionaries based on Intlayer configuration.",
|
|
6
6
|
"keywords": [
|
|
@@ -141,13 +141,13 @@
|
|
|
141
141
|
"typecheck": "tsc --noEmit --project tsconfig.types.json"
|
|
142
142
|
},
|
|
143
143
|
"dependencies": {
|
|
144
|
-
"@intlayer/api": "9.5.
|
|
145
|
-
"@intlayer/config": "9.5.
|
|
146
|
-
"@intlayer/core": "9.5.
|
|
147
|
-
"@intlayer/dictionaries-entry": "9.5.
|
|
148
|
-
"@intlayer/remote-dictionaries-entry": "9.5.
|
|
149
|
-
"@intlayer/types": "9.5.
|
|
150
|
-
"@intlayer/unmerged-dictionaries-entry": "9.5.
|
|
144
|
+
"@intlayer/api": "9.5.2",
|
|
145
|
+
"@intlayer/config": "9.5.2",
|
|
146
|
+
"@intlayer/core": "9.5.2",
|
|
147
|
+
"@intlayer/dictionaries-entry": "9.5.2",
|
|
148
|
+
"@intlayer/remote-dictionaries-entry": "9.5.2",
|
|
149
|
+
"@intlayer/types": "9.5.2",
|
|
150
|
+
"@intlayer/unmerged-dictionaries-entry": "9.5.2",
|
|
151
151
|
"@parcel/watcher": "2.6.0",
|
|
152
152
|
"defu": "6.1.7",
|
|
153
153
|
"fast-glob": "3.3.3",
|
|
@@ -156,7 +156,7 @@
|
|
|
156
156
|
"zod-to-ts": "2.1.0"
|
|
157
157
|
},
|
|
158
158
|
"devDependencies": {
|
|
159
|
-
"@types/node": "
|
|
159
|
+
"@types/node": "^22",
|
|
160
160
|
"@utils/ts-config": "1.0.4",
|
|
161
161
|
"@utils/ts-config-types": "1.0.4",
|
|
162
162
|
"@utils/tsdown-config": "1.0.4",
|
|
@@ -164,10 +164,10 @@
|
|
|
164
164
|
"tsdown": "0.23.0",
|
|
165
165
|
"typescript": "7.0.2",
|
|
166
166
|
"vitest": "5.0.0",
|
|
167
|
-
"zod": "4.
|
|
167
|
+
"zod": "4.6.2"
|
|
168
168
|
},
|
|
169
169
|
"peerDependencies": {
|
|
170
|
-
"@intlayer/babel": "9.5.
|
|
170
|
+
"@intlayer/babel": "9.5.2"
|
|
171
171
|
},
|
|
172
172
|
"peerDependenciesMeta": {
|
|
173
173
|
"@intlayer/babel": {
|