@catladder/cli 0.0.0-next-8b9ad5ca → 0.0.0-next-6a4450e0

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 (87) hide show
  1. package/dist/bundles/catenv/index.js +773 -95
  2. package/dist/bundles/cli/index.js +847 -121
  3. package/dist/cli/src/apps/cli/commands/project/commandConfigSecrets.js +22 -4
  4. package/dist/cli/src/apps/cli/commands/project/commandConfigSecrets.js.map +1 -1
  5. package/dist/cli/src/commands/project/commandSecretsSyncGithub.d.ts +11 -2
  6. package/dist/cli/src/commands/project/commandSecretsSyncGithub.js +37 -14
  7. package/dist/cli/src/commands/project/commandSecretsSyncGithub.js.map +1 -1
  8. package/dist/cli/src/utils/github.d.ts +6 -1
  9. package/dist/cli/src/utils/github.js +16 -9
  10. package/dist/cli/src/utils/github.js.map +1 -1
  11. package/dist/pipeline/src/backends/github/GithubBackend.d.ts +4 -1
  12. package/dist/pipeline/src/backends/github/GithubBackend.js +70 -12
  13. package/dist/pipeline/src/backends/github/GithubBackend.js.map +1 -1
  14. package/dist/pipeline/src/backends/github/ciVariables.js +3 -1
  15. package/dist/pipeline/src/backends/github/ciVariables.js.map +1 -1
  16. package/dist/pipeline/src/backends/github/createGithubJobs.d.ts +13 -3
  17. package/dist/pipeline/src/backends/github/createGithubJobs.js +80 -14
  18. package/dist/pipeline/src/backends/github/createGithubJobs.js.map +1 -1
  19. package/dist/pipeline/src/backends/github/githubReleaseJobs.d.ts +2 -1
  20. package/dist/pipeline/src/backends/github/githubReleaseJobs.js +46 -33
  21. package/dist/pipeline/src/backends/github/githubReleaseJobs.js.map +1 -1
  22. package/dist/pipeline/src/backends/github/index.d.ts +2 -1
  23. package/dist/pipeline/src/backends/github/index.js +2 -1
  24. package/dist/pipeline/src/backends/github/index.js.map +1 -1
  25. package/dist/pipeline/src/backends/github/scriptFiles.d.ts +25 -0
  26. package/dist/pipeline/src/backends/github/scriptFiles.js +50 -0
  27. package/dist/pipeline/src/backends/github/scriptFiles.js.map +1 -0
  28. package/dist/pipeline/src/backends/gitlab/GitlabBackend.d.ts +1 -0
  29. package/dist/pipeline/src/backends/gitlab/GitlabBackend.js +18 -8
  30. package/dist/pipeline/src/backends/gitlab/GitlabBackend.js.map +1 -1
  31. package/dist/pipeline/src/backends/gitlab/ciVariables.js +2 -1
  32. package/dist/pipeline/src/backends/gitlab/ciVariables.js.map +1 -1
  33. package/dist/pipeline/src/backends/gitlab/createGitlabJobs.d.ts +3 -2
  34. package/dist/pipeline/src/backends/gitlab/createGitlabJobs.js +84 -10
  35. package/dist/pipeline/src/backends/gitlab/createGitlabJobs.js.map +1 -1
  36. package/dist/pipeline/src/backends/gitlab/createGitlabPipeline.js +1 -1
  37. package/dist/pipeline/src/backends/gitlab/createGitlabPipeline.js.map +1 -1
  38. package/dist/pipeline/src/backends/gitlab/gitlabReleaseJobs.js +2 -2
  39. package/dist/pipeline/src/backends/gitlab/gitlabReleaseJobs.js.map +1 -1
  40. package/dist/pipeline/src/backends/types.d.ts +2 -1
  41. package/dist/pipeline/src/bash/ciVariables.d.ts +1 -1
  42. package/dist/pipeline/src/bash/ciVariables.js.map +1 -1
  43. package/dist/pipeline/src/build/node/yarn.js +4 -1
  44. package/dist/pipeline/src/build/node/yarn.js.map +1 -1
  45. package/dist/pipeline/src/build/types.d.ts +1 -1
  46. package/dist/pipeline/src/constants.js +1 -1
  47. package/dist/pipeline/src/context/getEnvironmentVariables.d.ts +8 -0
  48. package/dist/pipeline/src/context/getEnvironmentVariables.js +3 -3
  49. package/dist/pipeline/src/context/getEnvironmentVariables.js.map +1 -1
  50. package/dist/pipeline/src/context/transformJobOnlyVars.js +2 -2
  51. package/dist/pipeline/src/context/transformJobOnlyVars.js.map +1 -1
  52. package/dist/pipeline/src/context/utils/envVars.d.ts +7 -0
  53. package/dist/pipeline/src/context/utils/envVars.js +22 -1
  54. package/dist/pipeline/src/context/utils/envVars.js.map +1 -1
  55. package/dist/pipeline/src/customImages/hash.d.ts +15 -0
  56. package/dist/pipeline/src/customImages/hash.js +71 -0
  57. package/dist/pipeline/src/customImages/hash.js.map +1 -0
  58. package/dist/pipeline/src/customImages/jobImagesPlan.d.ts +59 -0
  59. package/dist/pipeline/src/customImages/jobImagesPlan.js +172 -0
  60. package/dist/pipeline/src/customImages/jobImagesPlan.js.map +1 -0
  61. package/dist/pipeline/src/customImages/shippedImages.d.ts +28 -0
  62. package/dist/pipeline/src/customImages/shippedImages.js +60 -0
  63. package/dist/pipeline/src/customImages/shippedImages.js.map +1 -0
  64. package/dist/pipeline/src/deploy/cloudRun/index.js +4 -1
  65. package/dist/pipeline/src/deploy/cloudRun/index.js.map +1 -1
  66. package/dist/pipeline/src/deploy/cloudRun/utils/database.js +4 -1
  67. package/dist/pipeline/src/deploy/cloudRun/utils/database.js.map +1 -1
  68. package/dist/pipeline/src/pipeline/generatePipelineFiles.js +16 -1
  69. package/dist/pipeline/src/pipeline/generatePipelineFiles.js.map +1 -1
  70. package/dist/pipeline/src/runner/index.d.ts +20 -1
  71. package/dist/pipeline/src/runner/index.js +16 -3
  72. package/dist/pipeline/src/runner/index.js.map +1 -1
  73. package/dist/pipeline/src/types/config.d.ts +26 -2
  74. package/dist/pipeline/src/types/github-types.d.ts +5 -0
  75. package/dist/pipeline/src/types/jobs.d.ts +10 -2
  76. package/dist/pipeline/src/types/jobs.js.map +1 -1
  77. package/dist/pipeline/src/utils/gitlab.d.ts +5 -0
  78. package/dist/pipeline/src/utils/gitlab.js +6 -0
  79. package/dist/pipeline/src/utils/gitlab.js.map +1 -1
  80. package/dist/pipeline/src/utils/writeFiles.d.ts +6 -0
  81. package/dist/pipeline/src/utils/writeFiles.js +8 -0
  82. package/dist/pipeline/src/utils/writeFiles.js.map +1 -1
  83. package/dist/tsconfig.tsbuildinfo +1 -1
  84. package/package.json +1 -1
  85. package/src/apps/cli/commands/project/commandConfigSecrets.ts +25 -5
  86. package/src/commands/project/commandSecretsSyncGithub.ts +34 -11
  87. package/src/utils/github.ts +24 -7
@@ -1522,7 +1522,11 @@ const createAllJobs_1 = __nccwpck_require__(3671);
1522
1522
  const config_1 = __nccwpck_require__(6925);
1523
1523
  const ciVariables_1 = __nccwpck_require__(3093);
1524
1524
  const createGithubJobs_1 = __nccwpck_require__(4016);
1525
+ const jobImagesPlan_1 = __nccwpck_require__(1585);
1526
+ const scriptFunctions_1 = __nccwpck_require__(5759);
1527
+ const utils_1 = __nccwpck_require__(595);
1525
1528
  const githubReleaseJobs_1 = __nccwpck_require__(5611);
1529
+ const scriptFiles_1 = __nccwpck_require__(9189);
1526
1530
  const WORKFLOWS_FOLDER = ".github/workflows";
1527
1531
  /**
1528
1532
  * generated workflow files are prefixed so that cleanup never touches
@@ -1567,18 +1571,29 @@ class GithubBackend {
1567
1571
  async cleanup() {
1568
1572
  const entries = await (0, promises_1.readdir)(WORKFLOWS_FOLDER).catch(() => []);
1569
1573
  await Promise.all(entries.filter(file => file.startsWith(GENERATED_FILE_PREFIX)).map(file => (0, promises_1.unlink)((0, path_1.join)(WORKFLOWS_FOLDER, file))));
1574
+ await (0, promises_1.rm)(scriptFiles_1.GITHUB_SCRIPTS_FOLDER, {
1575
+ recursive: true,
1576
+ force: true
1577
+ });
1570
1578
  }
1571
1579
  async createFiles(config) {
1572
- const workflows = await this.createWorkflows(config);
1573
- return Object.entries(workflows).map(([fileName, workflow]) => ({
1580
+ const images = this.createImagesPlan(config);
1581
+ const scripts = new scriptFiles_1.GithubScriptFiles();
1582
+ const workflows = await this.createWorkflows(config, images, scripts);
1583
+ return [...Object.entries(workflows).map(([fileName, workflow]) => ({
1574
1584
  path: (0, path_1.join)(WORKFLOWS_FOLDER, fileName),
1575
1585
  content: workflow
1576
- }));
1586
+ })),
1587
+ // materialized job scripts and image definitions (repo mode)
1588
+ ...scripts.getGeneratedFiles(), ...images.getGeneratedFiles()];
1589
+ }
1590
+ createImagesPlan(config) {
1591
+ return new jobImagesPlan_1.JobImagesPlan((0, jobImagesPlan_1.getJobImagesMode)(config, this.type), this.type);
1577
1592
  }
1578
1593
  /**
1579
1594
  * all workflows keyed by file name, mainly for testing purposes
1580
1595
  */
1581
- async createWorkflows(config) {
1596
+ async createWorkflows(config, images = this.createImagesPlan(config), scripts = new scriptFiles_1.GithubScriptFiles()) {
1582
1597
  const workflows = {};
1583
1598
  const reviewStopJobs = {};
1584
1599
  const manualJobs = {};
@@ -1589,13 +1604,14 @@ class GithubBackend {
1589
1604
  pipelineType: this.type
1590
1605
  });
1591
1606
  const uploadProviderIds = (0, createGithubJobs_1.getUploadProviderIds)(allJobs);
1607
+ const secretKinds = (0, createGithubJobs_1.collectSecretKinds)(allJobs);
1592
1608
  const jobs = {};
1593
1609
  // NOTE: agent jobs are gitlab-only and skipped on github
1594
1610
  [...allJobs.workspaces, ...allJobs.components].forEach(({
1595
1611
  context,
1596
1612
  jobs: contextJobs
1597
1613
  }) => contextJobs.forEach(job => {
1598
- const [id, githubJob] = (0, createGithubJobs_1.makeGithubJob)(context, job, allJobs, uploadProviderIds);
1614
+ const [id, githubJob] = (0, createGithubJobs_1.makeGithubJob)(context, job, allJobs, uploadProviderIds, images, scripts, secretKinds);
1599
1615
  if (isReviewStopJob(context, job)) {
1600
1616
  reviewStopJobs[id] = githubJob;
1601
1617
  } else if (isManualTaskJob(job)) {
@@ -1605,15 +1621,19 @@ class GithubBackend {
1605
1621
  }
1606
1622
  }));
1607
1623
  if (trigger === "mainBranch") {
1608
- const releaseJobs = (0, githubReleaseJobs_1.getGithubReleaseJobs)(config, Object.keys(jobs));
1624
+ const releaseJobs = (0, githubReleaseJobs_1.getGithubReleaseJobs)(config, Object.keys(jobs), images);
1609
1625
  Object.assign(jobs, releaseJobs.main);
1610
1626
  Object.assign(manualJobs, releaseJobs.manual);
1611
1627
  }
1612
1628
  if (Object.keys(jobs).length > 0) {
1613
1629
  workflows[`${GENERATED_FILE_PREFIX}${workflowFileName(trigger)}`] = {
1614
1630
  ...TRIGGER_WORKFLOWS[trigger],
1631
+ ...workflowPermissions(images),
1615
1632
  env: ciVariables_1.GITHUB_INJECTED_WORKFLOW_ENV,
1616
- jobs
1633
+ jobs: {
1634
+ ...makeEnsureImageGithubJobs(images),
1635
+ ...jobs
1636
+ }
1617
1637
  };
1618
1638
  }
1619
1639
  }
@@ -1625,8 +1645,12 @@ class GithubBackend {
1625
1645
  types: ["closed"]
1626
1646
  }
1627
1647
  },
1648
+ ...workflowPermissions(images),
1628
1649
  env: ciVariables_1.GITHUB_INJECTED_WORKFLOW_ENV,
1629
- jobs: reviewStopJobs
1650
+ jobs: {
1651
+ ...makeEnsureImageGithubJobs(images),
1652
+ ...reviewStopJobs
1653
+ }
1630
1654
  };
1631
1655
  }
1632
1656
  if (Object.keys(manualJobs).length > 0) {
@@ -1644,17 +1668,51 @@ class GithubBackend {
1644
1668
  }
1645
1669
  }
1646
1670
  },
1671
+ ...workflowPermissions(images),
1647
1672
  env: ciVariables_1.GITHUB_INJECTED_WORKFLOW_ENV,
1648
- jobs: Object.fromEntries(Object.entries(manualJobs).map(([id, job]) => [id, {
1649
- ...job,
1650
- if: `\${{ inputs.job == '${id}' }}`
1651
- }]))
1673
+ jobs: {
1674
+ ...makeEnsureImageGithubJobs(images),
1675
+ ...Object.fromEntries(Object.entries(manualJobs).map(([id, job]) => [id, {
1676
+ ...job,
1677
+ if: `\${{ inputs.job == '${id}' }}`
1678
+ }]))
1679
+ }
1652
1680
  };
1653
1681
  }
1654
1682
  return workflows;
1655
1683
  }
1656
1684
  }
1657
1685
  exports.GithubBackend = GithubBackend;
1686
+ /**
1687
+ * repo mode needs packages:write for pushing job images to ghcr
1688
+ */
1689
+ const workflowPermissions = images => images.mode === "repo" ? {
1690
+ permissions: {
1691
+ contents: "read",
1692
+ packages: "write"
1693
+ }
1694
+ } : {};
1695
+ /**
1696
+ * the image build jobs: always run (github has no server-side
1697
+ * changes-filter), the existence check makes unchanged runs fast.
1698
+ * Docker is native on github runners — no container, no dind.
1699
+ */
1700
+ const makeEnsureImageGithubJobs = images => Object.fromEntries(images.getEnsureJobs().map(job => {
1701
+ var _a, _b;
1702
+ return [(0, createGithubJobs_1.githubGlobalJobId)(job.name), {
1703
+ name: job.name,
1704
+ "runs-on": "ubuntu-latest",
1705
+ steps: [{
1706
+ name: "Checkout",
1707
+ uses: "actions/checkout@v4"
1708
+ }, {
1709
+ name: job.name,
1710
+ id: "main",
1711
+ run: [...(0, scriptFunctions_1.getGithubScriptFunctionDefinitions)(), ...((_b = (_a = job.script) === null || _a === void 0 ? void 0 : _a.filter(utils_1.notNil)) !== null && _b !== void 0 ? _b : [])].join("\n"),
1712
+ shell: "bash"
1713
+ }]
1714
+ }];
1715
+ }));
1658
1716
  const workflowFileName = trigger => {
1659
1717
  switch (trigger) {
1660
1718
  case "mr":
@@ -1705,7 +1763,8 @@ exports.GITHUB_CI_VARIABLES = {
1705
1763
  jobToken: "CL_JOB_TOKEN",
1706
1764
  jobImage: "CL_JOB_IMAGE",
1707
1765
  registry: "CL_REGISTRY",
1708
- registryImage: "CL_REGISTRY_IMAGE"
1766
+ registryImage: "CL_REGISTRY_IMAGE",
1767
+ registryUser: "CL_REGISTRY_USER"
1709
1768
  };
1710
1769
  /**
1711
1770
  * the workflow-level env the backend injects to provide the CL_*
@@ -1715,6 +1774,7 @@ exports.GITHUB_INJECTED_WORKFLOW_ENV = {
1715
1774
  CL_JOB_TOKEN: "${{ github.token }}",
1716
1775
  CL_REGISTRY: "ghcr.io",
1717
1776
  CL_REGISTRY_IMAGE: "ghcr.io/${{ github.repository }}",
1777
+ CL_REGISTRY_USER: "${{ github.actor }}",
1718
1778
  // empty outside of pull request events
1719
1779
  CL_PR_NUMBER: "${{ github.event.number }}"
1720
1780
  };
@@ -1730,7 +1790,7 @@ exports.GITHUB_INJECTED_WORKFLOW_ENV = {
1730
1790
  Object.defineProperty(exports, "__esModule", ({
1731
1791
  value: true
1732
1792
  }));
1733
- exports.getUploadProviderIds = exports.makeGithubJob = exports.resolveGithubNeeds = exports.githubJobId = void 0;
1793
+ exports.getUploadProviderIds = exports.makeGithubJob = exports.collectSecretKinds = exports.resolveGithubNeeds = exports.githubGlobalJobId = exports.githubJobId = void 0;
1734
1794
  const lodash_es_1 = __nccwpck_require__(4284);
1735
1795
  const getInjectVarsScript_1 = __nccwpck_require__(436);
1736
1796
  const createJobCache_1 = __nccwpck_require__(5644);
@@ -1738,6 +1798,7 @@ const getEnvironmentVariables_1 = __nccwpck_require__(5855);
1738
1798
  const utils_1 = __nccwpck_require__(595);
1739
1799
  const gitlab_1 = __nccwpck_require__(3468);
1740
1800
  const scriptFunctions_1 = __nccwpck_require__(5759);
1801
+ const ciVariables_1 = __nccwpck_require__(3093);
1741
1802
  /**
1742
1803
  * runner variables that only make sense on gitlab runners and must not
1743
1804
  * leak into github jobs (docker-in-docker daemon config, kubernetes
@@ -1756,6 +1817,9 @@ const slug = value => {
1756
1817
  };
1757
1818
  const githubJobId = (context, jobName) => slug(`${context.type === "workspace" ? "ws-" : ""}${context.name}-${jobName}-${context.env}`);
1758
1819
  exports.githubJobId = githubJobId;
1820
+ /** id of a job without component/workspace context (e.g. image builds) */
1821
+ const githubGlobalJobId = jobName => slug(jobName);
1822
+ exports.githubGlobalJobId = githubGlobalJobId;
1759
1823
  const githubJobName = (context, jobName) => `${context.name} ${jobName} | ${context.env}`;
1760
1824
  const resolveNeed = (context, need, allJobs) => {
1761
1825
  var _a;
@@ -1777,6 +1841,12 @@ const resolveNeed = (context, need, allJobs) => {
1777
1841
  return (0, exports.githubJobId)(provider.context, jobName);
1778
1842
  };
1779
1843
  if ((0, lodash_es_1.isObject)(need)) {
1844
+ if ("global" in need && need.global) {
1845
+ return {
1846
+ id: (0, exports.githubGlobalJobId)(need.job),
1847
+ artifacts: need.artifacts
1848
+ };
1849
+ }
1780
1850
  if ("workspaceName" in need && need.workspaceName) {
1781
1851
  return {
1782
1852
  id: findWorkspaceJob(need.workspaceName, need.job),
@@ -1794,9 +1864,9 @@ const resolveNeed = (context, need, allJobs) => {
1794
1864
  artifacts: true
1795
1865
  };
1796
1866
  };
1797
- const resolveGithubNeeds = (context, job, allJobs) => {
1867
+ const resolveGithubNeeds = (context, job, allJobs, extraNeeds = []) => {
1798
1868
  var _a;
1799
- const resolved = ((_a = job.needs) !== null && _a !== void 0 ? _a : []).map(need => resolveNeed(context, need, allJobs));
1869
+ const resolved = [...extraNeeds, ...((_a = job.needs) !== null && _a !== void 0 ? _a : [])].map(need => resolveNeed(context, need, allJobs));
1800
1870
  // deduplicate by provider id, explicit entries win (they come last)
1801
1871
  return [...new Map(resolved.map(n => [n.id, n])).values()].sort((a, b) => a.id.localeCompare(b.id));
1802
1872
  };
@@ -1837,29 +1907,73 @@ const createCacheSteps = (context, job) => {
1837
1907
  };
1838
1908
  });
1839
1909
  };
1840
- const getSecretsEnv = context => {
1910
+ /**
1911
+ * how a vault-stored value reaches the job env: masked github secret,
1912
+ * or plain (unmasked) github variable for non-sensitive values — those
1913
+ * stay readable in logs and usable in environment urls
1914
+ */
1915
+ const vaultValueExpression = (name, kinds) => kinds.get(name) === "variable" ? `\${{ vars.${name} }}` : `\${{ secrets.${name} }}`;
1916
+ /**
1917
+ * the kind of every vault-stored var of all components, keyed by its
1918
+ * CL_* name — the lookup for cross-component references
1919
+ */
1920
+ const collectSecretKinds = allJobs => {
1921
+ const kinds = new Map();
1922
+ allJobs.components.forEach(({
1923
+ context
1924
+ }) => {
1925
+ var _a, _b;
1926
+ const entries = [...context.environment.secretEnvVarKeys, ...((_a = context.environment.jobOnlyVars.build.secretEnvVarKeys) !== null && _a !== void 0 ? _a : []), ...((_b = context.environment.jobOnlyVars.deploy.secretEnvVarKeys) !== null && _b !== void 0 ? _b : [])];
1927
+ entries.forEach(({
1928
+ key,
1929
+ kind
1930
+ }) => kinds.set((0, getEnvironmentVariables_1.getSecretVarName)(context.env, context.name, key), kind !== null && kind !== void 0 ? kind : "secret"));
1931
+ });
1932
+ return kinds;
1933
+ };
1934
+ exports.collectSecretKinds = collectSecretKinds;
1935
+ const getSecretsEnv = (context, kinds) => {
1841
1936
  var _a, _b;
1842
1937
  const keys = [...context.environment.secretEnvVarKeys, ...((_a = context.environment.jobOnlyVars.build.secretEnvVarKeys) !== null && _a !== void 0 ? _a : []), ...((_b = context.environment.jobOnlyVars.deploy.secretEnvVarKeys) !== null && _b !== void 0 ? _b : [])];
1843
1938
  return Object.fromEntries(keys.map(({
1844
1939
  key
1845
1940
  }) => {
1846
1941
  const name = (0, getEnvironmentVariables_1.getSecretVarName)(context.env, context.name, key);
1847
- return [name, `\${{ secrets.${name} }}`];
1942
+ return [name, vaultValueExpression(name, kinds)];
1848
1943
  }));
1849
1944
  };
1850
- const makeGithubJob = (context, job, allJobs, uploadProviderIds) => {
1945
+ /**
1946
+ * every CL_* variable the job script reads must exist in the job env.
1947
+ * On gitlab all project variables are implicitly available in every
1948
+ * job — cross-component references (`${other-component:KEY}`) rely on
1949
+ * that. Github jobs only see what is injected explicitly, so the
1950
+ * script is scanned for the names it actually references.
1951
+ */
1952
+ const getReferencedSecretsEnv = (script, kinds) => Object.fromEntries([...script.matchAll(/\$\{?(CL_[A-Za-z0-9_]+)\}?/g)].map(match => match[1]).filter(name => !(name in ciVariables_1.GITHUB_INJECTED_WORKFLOW_ENV)).map(name => [name, vaultValueExpression(name, kinds)]));
1953
+ const makeGithubJob = (context, job, allJobs, uploadProviderIds, images, scripts, secretKinds = new Map()) => {
1851
1954
  var _a, _b, _c, _d, _e, _f, _g, _h, _j;
1852
1955
  const id = (0, exports.githubJobId)(context, job.name);
1853
1956
  const isComponent = context.type === "component";
1854
1957
  const createsJobEnv = !!job.environment && isComponent && (!job.environment.action || job.environment.action === "start");
1855
1958
  const skipCheckout = ((_a = job.runnerVariables) === null || _a === void 0 ? void 0 : _a.GIT_STRATEGY) === "none";
1856
- const needs = (0, exports.resolveGithubNeeds)(context, job, allJobs);
1857
- const image = typeof job.image === "string" ? job.image : Array.isArray(job.image) ? job.image[0] : (_b = job.image) === null || _b === void 0 ? void 0 : _b.name;
1959
+ const resolved = images.resolve(job.image);
1960
+ const needs = (0, exports.resolveGithubNeeds)(context, job, allJobs, resolved.need ? [resolved.need] : []);
1961
+ const image = typeof resolved.image === "string" ? resolved.image : Array.isArray(resolved.image) ? resolved.image[0] : (_b = resolved.image) === null || _b === void 0 ? void 0 : _b.name;
1858
1962
  const runScript = [...(0, scriptFunctions_1.getGithubScriptFunctionDefinitions)(), ...(0, gitlab_1.collapseableSection)("injectvars", "Injecting variables")((0, getInjectVarsScript_1.getInjectVarsScript)((_c = job.variables) !== null && _c !== void 0 ? _c : {})), ...((_e = (_d = job.script) === null || _d === void 0 ? void 0 : _d.filter(utils_1.notNil)) !== null && _e !== void 0 ? _e : []), ...(createsJobEnv ? [`echo "url=$ROOT_URL" >> "$GITHUB_OUTPUT"`] : [])].join("\n");
1963
+ // scripts live in committed files to keep workflows under github's
1964
+ // 512 KB limit. Two classes of jobs must keep their script inline:
1965
+ // jobs without a checkout have no repo files, and `${{ }}` expressions
1966
+ // are only interpolated inside the workflow file itself.
1967
+ const run = skipCheckout || runScript.includes("${{") ? runScript : scripts.add(id, runScript);
1859
1968
  const artifactPaths = (_g = (_f = job.artifacts) === null || _f === void 0 ? void 0 : _f.paths) !== null && _g !== void 0 ? _g : [];
1860
- const services = Object.fromEntries(((_h = job.services) !== null && _h !== void 0 ? _h : []).map(service => typeof service === "string" ? {
1969
+ const declaredServices = ((_h = job.services) !== null && _h !== void 0 ? _h : []).map(service => typeof service === "string" ? {
1861
1970
  name: service
1862
- } : service).filter(service => !isDindService(service.name)).map(service => [slug("alias" in service && service.alias ? service.alias : service.name), {
1971
+ } : service);
1972
+ // docker runs natively on github runners, so gitlab's docker-in-docker
1973
+ // services are dropped. The job container stays: the runner mounts the
1974
+ // host's docker socket into it, and images like docker-build carry the
1975
+ // helper scripts (ensure*Dockerfile etc.) the job scripts rely on.
1976
+ const services = Object.fromEntries(declaredServices.filter(service => !isDindService(service.name)).map(service => [slug("alias" in service && service.alias ? service.alias : service.name), {
1863
1977
  image: service.name,
1864
1978
  ...("variables" in service && service.variables ? {
1865
1979
  env: service.variables
@@ -1867,7 +1981,8 @@ const makeGithubJob = (context, job, allJobs, uploadProviderIds) => {
1867
1981
  }]));
1868
1982
  const env = {
1869
1983
  ...Object.fromEntries(Object.entries((_j = job.runnerVariables) !== null && _j !== void 0 ? _j : {}).filter(([key]) => !GITLAB_ONLY_RUNNER_VARIABLES.has(key))),
1870
- ...(isComponent ? getSecretsEnv(context) : {}),
1984
+ ...(isComponent ? getSecretsEnv(context, secretKinds) : {}),
1985
+ ...getReferencedSecretsEnv(runScript, secretKinds),
1871
1986
  CL_JOB_IMAGE: image !== null && image !== void 0 ? image : "runner"
1872
1987
  };
1873
1988
  const githubJob = {
@@ -1878,7 +1993,14 @@ const makeGithubJob = (context, job, allJobs, uploadProviderIds) => {
1878
1993
  } : {}),
1879
1994
  ...(image ? {
1880
1995
  container: {
1881
- image
1996
+ image,
1997
+ // private images in the repo's registry need credentials
1998
+ ...(resolved.fromRepoRegistry ? {
1999
+ credentials: {
2000
+ username: "${{ github.actor }}",
2001
+ password: "${{ github.token }}"
2002
+ }
2003
+ } : {})
1882
2004
  }
1883
2005
  } : {}),
1884
2006
  ...(Object.keys(services).length > 0 ? {
@@ -1905,7 +2027,7 @@ const makeGithubJob = (context, job, allJobs, uploadProviderIds) => {
1905
2027
  })), {
1906
2028
  name: job.name,
1907
2029
  id: "main",
1908
- run: runScript,
2030
+ run,
1909
2031
  shell: "bash"
1910
2032
  }, ...(uploadProviderIds.has(id) || artifactPaths.length > 0 ? artifactPaths.length > 0 ? [{
1911
2033
  name: "Upload artifacts",
@@ -1913,7 +2035,11 @@ const makeGithubJob = (context, job, allJobs, uploadProviderIds) => {
1913
2035
  with: {
1914
2036
  name: id,
1915
2037
  path: artifactPaths.join("\n"),
1916
- "if-no-files-found": "warn"
2038
+ "if-no-files-found": "warn",
2039
+ // gitlab artifacts include everything; upload-artifact
2040
+ // silently drops hidden files by default — which is
2041
+ // exactly where next.js builds live (.next)
2042
+ "include-hidden-files": true
1917
2043
  }
1918
2044
  }] : [] : [])]
1919
2045
  };
@@ -1946,36 +2072,49 @@ Object.defineProperty(exports, "__esModule", ({
1946
2072
  value: true
1947
2073
  }));
1948
2074
  exports.getGithubReleaseJobs = void 0;
1949
- const runner_1 = __nccwpck_require__(2348);
1950
- const createReleaseJob = needs => ({
1951
- name: "create release",
1952
- "runs-on": "ubuntu-latest",
1953
- container: {
1954
- image: (0, runner_1.getRunnerImage)("semantic-release")
1955
- },
1956
- // semantic-release pushes the version tag and release commit
1957
- permissions: {
1958
- contents: "write"
1959
- },
1960
- ...(needs && needs.length > 0 ? {
1961
- needs
1962
- } : {}),
1963
- env: {
1964
- GITHUB_TOKEN: "${{ github.token }}"
1965
- },
1966
- steps: [{
1967
- name: "Checkout",
1968
- uses: "actions/checkout@v4",
1969
- // semantic-release needs the full history to determine the version
1970
- with: {
1971
- "fetch-depth": 0
1972
- }
1973
- }, {
2075
+ const createGithubJobs_1 = __nccwpck_require__(4016);
2076
+ const createReleaseJob = (images, needs) => {
2077
+ const resolved = images.resolveRef({
2078
+ catladderImage: "semantic-release"
2079
+ });
2080
+ const imageNeeds = resolved.need && typeof resolved.need === "object" ? [(0, createGithubJobs_1.githubGlobalJobId)(resolved.need.job)] : [];
2081
+ const allNeeds = [...imageNeeds, ...(needs !== null && needs !== void 0 ? needs : [])];
2082
+ return {
1974
2083
  name: "create release",
1975
- run: "semanticRelease",
1976
- shell: "bash"
1977
- }]
1978
- });
2084
+ "runs-on": "ubuntu-latest",
2085
+ container: {
2086
+ image: resolved.image,
2087
+ ...(resolved.fromRepoRegistry ? {
2088
+ credentials: {
2089
+ username: "${{ github.actor }}",
2090
+ password: "${{ github.token }}"
2091
+ }
2092
+ } : {})
2093
+ },
2094
+ // semantic-release pushes the version tag and release commit
2095
+ permissions: {
2096
+ contents: "write"
2097
+ },
2098
+ ...(allNeeds.length > 0 ? {
2099
+ needs: allNeeds
2100
+ } : {}),
2101
+ env: {
2102
+ GITHUB_TOKEN: "${{ github.token }}"
2103
+ },
2104
+ steps: [{
2105
+ name: "Checkout",
2106
+ uses: "actions/checkout@v4",
2107
+ // semantic-release needs the full history to determine the version
2108
+ with: {
2109
+ "fetch-depth": 0
2110
+ }
2111
+ }, {
2112
+ name: "create release",
2113
+ run: "semanticRelease",
2114
+ shell: "bash"
2115
+ }]
2116
+ };
2117
+ };
1979
2118
  /**
1980
2119
  * the release jobs for the github backend (the counterpart of the
1981
2120
  * gitlab release jobs):
@@ -1985,22 +2124,22 @@ const createReleaseJob = needs => ({
1985
2124
  * - otherwise (manual, the default): the release job goes to the
1986
2125
  * manual tasks workflow
1987
2126
  */
1988
- const getGithubReleaseJobs = (config, mainWorkflowJobIds) => {
2127
+ const getGithubReleaseJobs = (config, mainWorkflowJobIds, images) => {
1989
2128
  var _a;
1990
2129
  if (((_a = config.releases) === null || _a === void 0 ? void 0 : _a.when) === "auto") {
1991
2130
  return {
1992
2131
  main: {
1993
- "create-release": createReleaseJob(mainWorkflowJobIds)
2132
+ "create-release": createReleaseJob(images, mainWorkflowJobIds)
1994
2133
  },
1995
2134
  manual: {
1996
- "force-create-release": createReleaseJob()
2135
+ "force-create-release": createReleaseJob(images)
1997
2136
  }
1998
2137
  };
1999
2138
  }
2000
2139
  return {
2001
2140
  main: {},
2002
2141
  manual: {
2003
- "create-release": createReleaseJob()
2142
+ "create-release": createReleaseJob(images)
2004
2143
  }
2005
2144
  };
2006
2145
  };
@@ -2038,6 +2177,64 @@ Object.defineProperty(exports, "__esModule", ({
2038
2177
  }));
2039
2178
  __exportStar(__nccwpck_require__(4038), exports);
2040
2179
  __exportStar(__nccwpck_require__(3093), exports);
2180
+ __exportStar(__nccwpck_require__(9189), exports);
2181
+
2182
+ /***/ }),
2183
+
2184
+ /***/ 9189:
2185
+ /***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
2186
+
2187
+ "use strict";
2188
+
2189
+
2190
+ Object.defineProperty(exports, "__esModule", ({
2191
+ value: true
2192
+ }));
2193
+ exports.GithubScriptFiles = exports.GITHUB_SCRIPTS_FOLDER = void 0;
2194
+ const path_1 = __nccwpck_require__(6928);
2195
+ /**
2196
+ * folder for job scripts materialized into the repository (committed,
2197
+ * like the image definitions). Owned entirely by the github backend.
2198
+ */
2199
+ exports.GITHUB_SCRIPTS_FOLDER = ".catladder-generated/github/scripts";
2200
+ /**
2201
+ * github silently ignores workflow files larger than 512 KB — no run,
2202
+ * no check suite, no error. Inlining every job's bash script blows past
2203
+ * that limit on bigger projects, so job scripts are materialized into
2204
+ * committed files and the workflow step only invokes them.
2205
+ *
2206
+ * Scripts containing `${{ ... }}` expressions must stay inline: the
2207
+ * runner only interpolates expressions in the workflow file itself,
2208
+ * never in files read at runtime.
2209
+ */
2210
+ class GithubScriptFiles {
2211
+ constructor() {
2212
+ this.files = new Map();
2213
+ }
2214
+ /**
2215
+ * materializes a job script into a committed file and returns the
2216
+ * step command invoking it
2217
+ */
2218
+ add(jobId, script) {
2219
+ const path = (0, path_1.join)(exports.GITHUB_SCRIPTS_FOLDER, `${jobId}.sh`);
2220
+ // `shell: bash` steps run with `bash -eo pipefail`; the script file
2221
+ // must fail exactly like the inline version did
2222
+ const content = `#!/usr/bin/env bash\nset -eo pipefail\n\n${script}\n`;
2223
+ const existing = this.files.get(path);
2224
+ if (existing !== undefined && existing !== content) {
2225
+ throw new Error(`conflicting scripts for job id '${jobId}'`);
2226
+ }
2227
+ this.files.set(path, content);
2228
+ return `bash ${path}`;
2229
+ }
2230
+ getGeneratedFiles() {
2231
+ return [...this.files.entries()].map(([path, content]) => ({
2232
+ path,
2233
+ content
2234
+ }));
2235
+ }
2236
+ }
2237
+ exports.GithubScriptFiles = GithubScriptFiles;
2041
2238
 
2042
2239
  /***/ }),
2043
2240
 
@@ -2092,6 +2289,7 @@ const promises_1 = __nccwpck_require__(1943);
2092
2289
  const rules_1 = __nccwpck_require__(4565);
2093
2290
  const config_1 = __nccwpck_require__(6925);
2094
2291
  const createAllJobs_1 = __nccwpck_require__(3671);
2292
+ const jobImagesPlan_1 = __nccwpck_require__(1585);
2095
2293
  const createGitlabJobs_1 = __nccwpck_require__(512);
2096
2294
  const createGitlabPipeline_1 = __nccwpck_require__(1862);
2097
2295
  const gitlabReleaseJobs_1 = __nccwpck_require__(5531);
@@ -2111,31 +2309,38 @@ class GitlabBackend {
2111
2309
  });
2112
2310
  }
2113
2311
  async createFiles(config) {
2114
- const includes = await this.createIncludes(config);
2312
+ const images = this.createImagesPlan(config);
2313
+ const includes = await this.createIncludes(config, images);
2115
2314
  const mainFile = {
2116
2315
  path: ".gitlab-ci.yml",
2117
2316
  content: {
2118
2317
  include: includes.map(i => i.path)
2119
2318
  }
2120
2319
  };
2121
- return [mainFile, ...includes];
2320
+ return [mainFile, ...includes,
2321
+ // materialized image definitions (repo mode)
2322
+ ...images.getGeneratedFiles()];
2323
+ }
2324
+ createImagesPlan(config) {
2325
+ return new jobImagesPlan_1.JobImagesPlan((0, jobImagesPlan_1.getJobImagesMode)(config, this.type), this.type);
2122
2326
  }
2123
2327
  /**
2124
2328
  * the complete pipeline as one object (all includes merged into one),
2125
2329
  * mainly for testing purposes
2126
2330
  */
2127
2331
  async createCompletePipeline(config) {
2128
- const includes = await this.createIncludes(config);
2332
+ const includes = await this.createIncludes(config, this.createImagesPlan(config));
2129
2333
  return includes.reduce((acc, {
2130
2334
  content
2131
2335
  }) => {
2132
2336
  return {
2133
2337
  ...acc,
2338
+ // includes are always yaml objects
2134
2339
  ...content // merge all includes into one object
2135
2340
  };
2136
2341
  }, {});
2137
2342
  }
2138
- async createIncludes(config) {
2343
+ async createIncludes(config, images) {
2139
2344
  const {
2140
2345
  jobs,
2141
2346
  image,
@@ -2143,7 +2348,7 @@ class GitlabBackend {
2143
2348
  variables,
2144
2349
  workflow,
2145
2350
  ...pipelineRest
2146
- } = await this.createPipeline(config);
2351
+ } = await this.createPipeline(config, images);
2147
2352
  // we will create 1 include per component or workspace
2148
2353
  // this is for better readability in git diffs and to avoid problems with yaml files beeing too large
2149
2354
  // group by context
@@ -2176,14 +2381,14 @@ class GitlabBackend {
2176
2381
  };
2177
2382
  return [mainInclude, ...componentIncludes];
2178
2383
  }
2179
- async createPipeline(config) {
2384
+ async createPipeline(config, images) {
2180
2385
  const stages = (0, getPipelineStages_1.getPipelineStages)(config);
2181
2386
  // for all triggers create jobs and add base rules
2182
2387
  const allJobsPerTrigger = await Promise.all(config_1.ALL_PIPELINE_TRIGGERS.map(async trigger => await (0, createGitlabJobs_1.createGitlabJobs)(await (0, createAllJobs_1.createAllJobs)({
2183
2388
  config,
2184
2389
  trigger,
2185
2390
  pipelineType: this.type
2186
- }), getGitlabRulesForTrigger(trigger)))).then(j => j.flat());
2391
+ }), images, getGitlabRulesForTrigger(trigger)))).then(j => j.flat());
2187
2392
  const allWorkspaceJobs = allJobsPerTrigger.filter(j => {
2188
2393
  var _a;
2189
2394
  return ((_a = j.context) === null || _a === void 0 ? void 0 : _a.type) === "workspace";
@@ -2212,7 +2417,9 @@ class GitlabBackend {
2212
2417
  var _a;
2213
2418
  return ((_a = j.context) === null || _a === void 0 ? void 0 : _a.type) === "agent";
2214
2419
  });
2215
- const allJobs = [...allWorkspaceJobs, ...allComponentJobs, ...allGlobalJobs].reduce((acc, {
2420
+ // context-less jobs (image build jobs)
2421
+ const allContextlessJobs = allJobsPerTrigger.filter(j => j.context === null);
2422
+ const allJobs = [...allContextlessJobs, ...allWorkspaceJobs, ...allComponentJobs, ...allGlobalJobs].reduce((acc, {
2216
2423
  gitlabJob,
2217
2424
  name,
2218
2425
  context
@@ -2326,7 +2533,8 @@ exports.GITLAB_CI_VARIABLES = {
2326
2533
  jobToken: "CI_JOB_TOKEN",
2327
2534
  jobImage: "CI_JOB_IMAGE",
2328
2535
  registry: "CI_REGISTRY",
2329
- registryImage: "CI_REGISTRY_IMAGE"
2536
+ registryImage: "CI_REGISTRY_IMAGE",
2537
+ registryUser: "CI_REGISTRY_USER"
2330
2538
  };
2331
2539
 
2332
2540
  /***/ }),
@@ -2395,7 +2603,7 @@ const getFullReferencedJobNameFromWorkspace = (referencedJobName, workspaceName,
2395
2603
  });
2396
2604
  };
2397
2605
  const getJobName = need => (0, lodash_es_1.isObject)(need) ? need.job : need;
2398
- const makeGitlabJob = (context, job, allJobs, baseRules) => {
2606
+ const makeGitlabJob = (context, job, allJobs, images, baseRules) => {
2399
2607
  var _a, _b;
2400
2608
  const {
2401
2609
  environment,
@@ -2413,8 +2621,14 @@ const makeGitlabJob = (context, job, allJobs, baseRules) => {
2413
2621
  allow_failure: jobAllowFailure,
2414
2622
  cache: jobCache,
2415
2623
  caches,
2624
+ image: jobImage,
2416
2625
  ...rest
2417
2626
  } = job;
2627
+ // catladder image references resolve according to the jobImages mode;
2628
+ // in repo mode the job additionally depends on the image build job
2629
+ const resolvedImage = images.resolve(jobImage);
2630
+ const image = resolvedImage.image;
2631
+ const imageNeeds = resolvedImage.need ? [resolvedImage.need] : [];
2418
2632
  // the neutral `gate` translates to gitlab's when/allow_failure;
2419
2633
  // explicitly set gitlab fields take precedence
2420
2634
  const when = jobWhen !== null && jobWhen !== void 0 ? jobWhen : gate ? gate === "manual" ? "manual" : "on_success" : undefined;
@@ -2423,7 +2637,7 @@ const makeGitlabJob = (context, job, allJobs, baseRules) => {
2423
2637
  // an explicitly set gitlab `cache` takes precedence
2424
2638
  const cache = jobCache !== null && jobCache !== void 0 ? jobCache : caches && context.type !== "agent" ? (0, createJobCache_1.createJobCacheFromCacheConfigs)(context, caches) : undefined;
2425
2639
  const stage = envMode === "stagePerEnv" && context.type !== "agent" ? `${job.stage} ${context.env}` : job.stage;
2426
- const deduplicatedGitlabNeeds = getGitlabNeeds(context, job, allJobs);
2640
+ const deduplicatedGitlabNeeds = getGitlabNeeds(context, job, allJobs, imageNeeds);
2427
2641
  const fullJobName = getFullJobName({
2428
2642
  type: context.type,
2429
2643
  name,
@@ -2452,6 +2666,7 @@ const makeGitlabJob = (context, job, allJobs, baseRules) => {
2452
2666
  retry: defaults_1.BASE_RETRY,
2453
2667
  interruptible: true,
2454
2668
  ...rest,
2669
+ image,
2455
2670
  allow_failure,
2456
2671
  cache: cache ? (0, cache_1.addCacheFallback)(cache, context) : undefined,
2457
2672
  rules: rules.length > 0 ? rules : undefined,
@@ -2527,13 +2742,13 @@ const addGitlabEnvironment = (context, catladderJobEnvironment, job, allJobs) =>
2527
2742
  script: [...((_b = job.script) !== null && _b !== void 0 ? _b : []), ...(createsJobEnv ? scriptToAdd : [])]
2528
2743
  };
2529
2744
  };
2530
- const createGitlabJobs = async (allJobs, baseRules) => {
2531
- return [...allJobs.workspaces, ...allJobs.components, ...allJobs.agents].flatMap(({
2745
+ const createGitlabJobs = async (allJobs, images, baseRules) => {
2746
+ const contextualJobs = [...allJobs.workspaces, ...allJobs.components, ...allJobs.agents].flatMap(({
2532
2747
  context,
2533
2748
  jobs
2534
2749
  }) => {
2535
2750
  return jobs.map(job => {
2536
- const [fullJobName, gitlabJob] = (0, exports.makeGitlabJob)(context, job, allJobs, baseRules);
2751
+ const [fullJobName, gitlabJob] = (0, exports.makeGitlabJob)(context, job, allJobs, images, baseRules);
2537
2752
  return {
2538
2753
  name: fullJobName,
2539
2754
  gitlabJob,
@@ -2541,13 +2756,66 @@ const createGitlabJobs = async (allJobs, baseRules) => {
2541
2756
  };
2542
2757
  });
2543
2758
  });
2759
+ // image build jobs (repo mode): no context, plain names, their
2760
+ // rules:changes combined with the trigger rules
2761
+ const imageJobs = images.getEnsureJobs().map(job => ({
2762
+ name: job.name,
2763
+ gitlabJob: makeEnsureImageGitlabJob(job, baseRules),
2764
+ context: null
2765
+ }));
2766
+ return [...imageJobs, ...contextualJobs];
2544
2767
  };
2545
2768
  exports.createGitlabJobs = createGitlabJobs;
2546
- function getGitlabNeeds(context, job, allJobs) {
2769
+ /**
2770
+ * lowers an image build job: only runs when the (generated) image
2771
+ * definition changed, within the trigger's base rules
2772
+ */
2773
+ const makeEnsureImageGitlabJob = (job, baseRules) => {
2774
+ var _a, _b, _c, _d;
2775
+ const changes = (_b = (_a = job.rules) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.changes;
2776
+ const rules = (baseRules !== null && baseRules !== void 0 ? baseRules : []).map(baseRule => baseRule.when === "never" ? baseRule : {
2777
+ ...baseRule,
2778
+ changes
2779
+ });
2780
+ return (0, removeUndefined_1.removeUndefined)({
2781
+ retry: defaults_1.BASE_RETRY,
2782
+ interruptible: true,
2783
+ image: job.image,
2784
+ services: job.services,
2785
+ rules: rules.length > 0 ? rules : [{
2786
+ changes
2787
+ }],
2788
+ variables: job.runnerVariables,
2789
+ script: (_d = (_c = job.script) === null || _c === void 0 ? void 0 : _c.filter(utils_1.notNil)) !== null && _d !== void 0 ? _d : [],
2790
+ stage: job.stage,
2791
+ needs: []
2792
+ });
2793
+ };
2794
+ function getGitlabNeeds(context, job, allJobs, extraNeeds = []) {
2547
2795
  var _a;
2548
- const needs = context.type === "workspace" ? getGitlabNeedsForWorkspaceJob(context, job, allJobs) : context.type === "agent" ? (_a = job.needs) !== null && _a !== void 0 ? _a : null : getGitlabNeedsForComponentJob(context, job, allJobs);
2796
+ const jobWithExtraNeeds = extraNeeds.length > 0 ? {
2797
+ ...job,
2798
+ needs: [...extraNeeds, ...((_a = job.needs) !== null && _a !== void 0 ? _a : [])]
2799
+ } : job;
2800
+ const needs = context.type === "workspace" ? getGitlabNeedsForWorkspaceJob(context, jobWithExtraNeeds, allJobs) : context.type === "agent" ? mapAgentNeeds(jobWithExtraNeeds.needs) : getGitlabNeedsForComponentJob(context, jobWithExtraNeeds, allJobs);
2549
2801
  return needs ? deduplicateNeeds(needs) : undefined;
2550
2802
  }
2803
+ /**
2804
+ * agent jobs use their needs as-is; global needs (image build jobs)
2805
+ * keep their plain name and optional flag
2806
+ */
2807
+ function mapAgentNeeds(needs) {
2808
+ if (!needs) {
2809
+ return null;
2810
+ }
2811
+ return needs.map(n => (0, lodash_es_1.isObject)(n) && "global" in n && n.global ? {
2812
+ job: n.job,
2813
+ artifacts: n.artifacts,
2814
+ ...(n.optional ? {
2815
+ optional: true
2816
+ } : {})
2817
+ } : n);
2818
+ }
2551
2819
  function deduplicateNeeds(needs) {
2552
2820
  return needs ? [...new Map(needs.map(n => [(0, lodash_es_1.isObject)(n) ? n.job : n, n])).values()] : undefined;
2553
2821
  }
@@ -2556,11 +2824,19 @@ function getGitlabNeedsForComponentJob(context, {
2556
2824
  }, allJobs) {
2557
2825
  return (needs !== null && needs !== void 0 ? needs : []).map(n => {
2558
2826
  var _a;
2559
- return (0, lodash_es_1.isObject)(n) ? "workspaceName" in n ? {
2827
+ return (0, lodash_es_1.isObject)(n) ? "global" in n && n.global ?
2828
+ // global jobs (e.g. image builds) keep their plain name
2829
+ {
2830
+ job: n.job,
2831
+ artifacts: n.artifacts,
2832
+ ...(n.optional ? {
2833
+ optional: true
2834
+ } : {})
2835
+ } : "workspaceName" in n && n.workspaceName ? {
2560
2836
  job: getFullReferencedJobNameFromWorkspace(n.job, n.workspaceName, context.env, allJobs),
2561
2837
  artifacts: n.artifacts
2562
2838
  } : {
2563
- job: getFullReferencedJobNameFromComponent(n.job, (_a = n.componentName) !== null && _a !== void 0 ? _a : context.name, context.env, allJobs),
2839
+ job: getFullReferencedJobNameFromComponent(n.job, "componentName" in n ? (_a = n.componentName) !== null && _a !== void 0 ? _a : context.name : context.name, context.env, allJobs),
2564
2840
  artifacts: n.artifacts
2565
2841
  } : getFullReferencedJobNameFromComponent(n, context.name, context.env, allJobs);
2566
2842
  }) // sort in a predictable manner for snapshot tests
@@ -2573,7 +2849,13 @@ function getGitlabNeedsForComponentJob(context, {
2573
2849
  function getGitlabNeedsForWorkspaceJob(context, {
2574
2850
  needs
2575
2851
  }, allJobs) {
2576
- return (needs !== null && needs !== void 0 ? needs : []).map(n => (0, lodash_es_1.isObject)(n) ? {
2852
+ return (needs !== null && needs !== void 0 ? needs : []).map(n => (0, lodash_es_1.isObject)(n) ? "global" in n && n.global ? {
2853
+ job: n.job,
2854
+ artifacts: n.artifacts,
2855
+ ...(n.optional ? {
2856
+ optional: true
2857
+ } : {})
2858
+ } : {
2577
2859
  job: getFullReferencedJobNameFromWorkspace(n.job, "workspaceName" in n && n.workspaceName ? n.workspaceName : context.name, context.env, allJobs),
2578
2860
  artifacts: n.artifacts
2579
2861
  } : getFullReferencedJobNameFromWorkspace(n, context.name, context.env, allJobs)) // sort in a predictable manner for snapshot tests
@@ -2602,7 +2884,7 @@ const createGitlabPipelineWithDefaults = ({
2602
2884
  ...config
2603
2885
  }) => {
2604
2886
  return {
2605
- image: image !== null && image !== void 0 ? image : (0, runner_1.getRunnerImage)("jobs-default"),
2887
+ image: image !== null && image !== void 0 ? image : (0, runner_1.getCentralRunnerImageUrl)("jobs-default"),
2606
2888
  // default image
2607
2889
  variables: {
2608
2890
  FF_USE_FASTZIP: "true",
@@ -2705,7 +2987,7 @@ const getGitlabReleaseJobs = config => {
2705
2987
  return {
2706
2988
  ["create release"]: {
2707
2989
  stage: "release",
2708
- image: (0, runner_1.getRunnerImage)("semantic-release"),
2990
+ image: (0, runner_1.getCentralRunnerImageUrl)("semantic-release"),
2709
2991
  script: ["semanticRelease"],
2710
2992
  after_script: [EXPIRED_TOKEN_HELP],
2711
2993
  rules: [...baseReleaseRules, {
@@ -2718,7 +3000,7 @@ const getGitlabReleaseJobs = config => {
2718
3000
  },
2719
3001
  ["⚠️ force create release"]: {
2720
3002
  stage: "release",
2721
- image: (0, runner_1.getRunnerImage)("semantic-release"),
3003
+ image: (0, runner_1.getCentralRunnerImageUrl)("semantic-release"),
2722
3004
  script: ["semanticRelease"],
2723
3005
  after_script: [EXPIRED_TOKEN_HELP],
2724
3006
  needs: [],
@@ -4461,7 +4743,10 @@ const getYarnInstallCommand = async context => {
4461
4743
  // inline builds make debugging easier as it prints it out in the logs, instead of writing it in temp files
4462
4744
  return `yarn install --immutable --inline-builds`;
4463
4745
  };
4464
- const ensureNodeVersion = context => (0, gitlab_1.collapseableSection)("nodeinstall", "Ensure node version")(["if [ -f ~/.nvm/nvm.sh ]; then source ~/.nvm/nvm.sh; fi", "if command -v nvm &> /dev/null && [ -f ./.nvmrc ]; then nvm install; fi"]);
4746
+ const ensureNodeVersion = context => (0, gitlab_1.collapseableSection)("nodeinstall", "Ensure node version")([
4747
+ // github actions overrides HOME in container jobs, so also look
4748
+ // for nvm where the catladder images install it (/root/.nvm)
4749
+ 'if [ -f "$HOME/.nvm/nvm.sh" ]; then source "$HOME/.nvm/nvm.sh"; elif [ -f /root/.nvm/nvm.sh ]; then export NVM_DIR=/root/.nvm; source /root/.nvm/nvm.sh; fi', "if command -v nvm &> /dev/null && [ -f ./.nvmrc ]; then nvm install; fi"]);
4465
4750
  exports.ensureNodeVersion = ensureNodeVersion;
4466
4751
  const getYarnInstall = async (context, options) => {
4467
4752
  const postInstall = context.type !== "workspace" && context.build.type !== "disabled" && "postInstall" in context.build.config ? context.build.config.postInstall : null;
@@ -4840,7 +5125,7 @@ Object.defineProperty(exports, "__esModule", ({
4840
5125
  value: true
4841
5126
  }));
4842
5127
  exports.DOCKER_REGISTRY = exports.PIPELINE_IMAGE_TAG = void 0;
4843
- exports.PIPELINE_IMAGE_TAG = "next-8b9ad5ca" || 0;
5128
+ exports.PIPELINE_IMAGE_TAG = "next-6a4450e0" || 0;
4844
5129
  exports.DOCKER_REGISTRY = "git.panter.ch:5001/catladder/catladder" || 0;
4845
5130
 
4846
5131
  /***/ }),
@@ -5252,7 +5537,7 @@ const getBasePredefinedVariables = ctx => {
5252
5537
  };
5253
5538
  };
5254
5539
  const getEnvironmentVariables = async (ctx, options = {}) => {
5255
- var _a, _b, _c, _d, _e, _f, _g, _h;
5540
+ var _a, _b, _c, _d, _e, _f, _g;
5256
5541
  const environmentContext = (0, getEnvironmentContext_1.getEnvironmentContext)(ctx);
5257
5542
  const {
5258
5543
  config,
@@ -5309,13 +5594,13 @@ const getEnvironmentVariables = async (ctx, options = {}) => {
5309
5594
  }
5310
5595
  const publicEnvVarsRaw = (_e = (_d = envConfigRaw.vars) === null || _d === void 0 ? void 0 : _d.public) !== null && _e !== void 0 ? _e : {};
5311
5596
  const additionalSecretKeys = deployConfigRaw ? deploy_1.DEPLOY_TYPES[deployConfigRaw.type].additionalSecretKeys(environmentContext) : [];
5312
- const secretEnvVarKeys = [...(0, envVars_1.stringListToSecreteEnvVarList)((_g = (_f = envConfigRaw.vars) === null || _f === void 0 ? void 0 : _f.secret) !== null && _g !== void 0 ? _g : []), ...additionalSecretKeys];
5597
+ const secretEnvVarKeys = [...(0, envVars_1.normalizeSecretVarsConfig)((_f = envConfigRaw.vars) === null || _f === void 0 ? void 0 : _f.secret), ...additionalSecretKeys];
5313
5598
  const secretEnvVars = (0, envVars_1.makeSecretEnvVarMapping)(env, componentName, secretEnvVarKeys);
5314
5599
  const publicEnvVarsRawSanitized = (0, envVars_1.stringifyValues)(publicEnvVarsRaw);
5315
5600
  const publicEnvVarsUnresolved = Object.fromEntries(Object.entries(publicEnvVarsRawSanitized).map(([key, value]) => [key, (0, VariableValueContainingReferences_1.createVariableValueContainingReferencesFromString)(value, {
5316
5601
  componentName: ctx.componentName
5317
5602
  })]));
5318
- const publicEnvVars = ((_h = options.shouldResolveReferences) !== null && _h !== void 0 ? _h : true) ? await (0, resolveAllReferences_1.resolveAllReferences)(publicEnvVarsUnresolved, async otherComponentName => {
5603
+ const publicEnvVars = ((_g = options.shouldResolveReferences) !== null && _g !== void 0 ? _g : true) ? await (0, resolveAllReferences_1.resolveAllReferences)(publicEnvVarsUnresolved, async otherComponentName => {
5319
5604
  const {
5320
5605
  envVars: otherEnvVars
5321
5606
  } = await (0, exports.getEnvironmentVariables)({
@@ -5475,7 +5760,7 @@ const envVars_1 = __nccwpck_require__(4946);
5475
5760
  * @returns
5476
5761
  */
5477
5762
  const transformJobOnlyVars = async (env, componentName, vars) => {
5478
- var _a, _b;
5763
+ var _a;
5479
5764
  if (!vars) {
5480
5765
  return {
5481
5766
  envVars: {},
@@ -5483,7 +5768,7 @@ const transformJobOnlyVars = async (env, componentName, vars) => {
5483
5768
  };
5484
5769
  }
5485
5770
  const publicVars = (0, envVars_1.stringifyValues)((_a = vars === null || vars === void 0 ? void 0 : vars.public) !== null && _a !== void 0 ? _a : {});
5486
- const secretEnvVarKeys = (0, envVars_1.stringListToSecreteEnvVarList)((_b = vars === null || vars === void 0 ? void 0 : vars.secret) !== null && _b !== void 0 ? _b : []);
5771
+ const secretEnvVarKeys = (0, envVars_1.normalizeSecretVarsConfig)(vars === null || vars === void 0 ? void 0 : vars.secret);
5487
5772
  const deployJobOnlySecretEnvVars = (vars === null || vars === void 0 ? void 0 : vars.secret) ? (0, envVars_1.makeSecretEnvVarMapping)(env, componentName, secretEnvVarKeys) : {};
5488
5773
  const envVars = {
5489
5774
  ...deployJobOnlySecretEnvVars,
@@ -5507,7 +5792,7 @@ exports.transformJobOnlyVars = transformJobOnlyVars;
5507
5792
  Object.defineProperty(exports, "__esModule", ({
5508
5793
  value: true
5509
5794
  }));
5510
- exports.makeSecretEnvVarMapping = exports.stringListToSecreteEnvVarList = exports.stringifyValues = void 0;
5795
+ exports.makeSecretEnvVarMapping = exports.normalizeSecretVarsConfig = exports.stringListToSecreteEnvVarList = exports.stringifyValues = void 0;
5511
5796
  const lodash_es_1 = __nccwpck_require__(4284);
5512
5797
  const getEnvironmentVariables_1 = __nccwpck_require__(5855);
5513
5798
  const BashExpression_1 = __nccwpck_require__(5354);
@@ -5517,6 +5802,27 @@ const stringListToSecreteEnvVarList = keys => keys.map(key => ({
5517
5802
  key
5518
5803
  }));
5519
5804
  exports.stringListToSecreteEnvVarList = stringListToSecreteEnvVarList;
5805
+ /**
5806
+ * normalizes both config forms of vault-stored env vars: the deprecated
5807
+ * plain string[] (all keys secret) and the record form carrying a kind
5808
+ * (or config object) per key
5809
+ */
5810
+ const normalizeSecretVarsConfig = secret => {
5811
+ if (!secret) {
5812
+ return [];
5813
+ }
5814
+ if (Array.isArray(secret)) {
5815
+ return (0, exports.stringListToSecreteEnvVarList)(secret);
5816
+ }
5817
+ return Object.entries(secret).map(([key, config]) => typeof config === "string" ? {
5818
+ key,
5819
+ kind: config
5820
+ } : {
5821
+ key,
5822
+ ...config
5823
+ });
5824
+ };
5825
+ exports.normalizeSecretVarsConfig = normalizeSecretVarsConfig;
5520
5826
  const makeSecretEnvVarMapping = (env, componentName, secretEnvVars) => {
5521
5827
  return Object.fromEntries(secretEnvVars.map(({
5522
5828
  key
@@ -5526,6 +5832,330 @@ exports.makeSecretEnvVarMapping = makeSecretEnvVarMapping;
5526
5832
 
5527
5833
  /***/ }),
5528
5834
 
5835
+ /***/ 3231:
5836
+ /***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
5837
+
5838
+ "use strict";
5839
+
5840
+
5841
+ Object.defineProperty(exports, "__esModule", ({
5842
+ value: true
5843
+ }));
5844
+ exports.computeCustomImageHash = void 0;
5845
+ const crypto_1 = __nccwpck_require__(6982);
5846
+ const fs_1 = __nccwpck_require__(9896);
5847
+ const path_1 = __nccwpck_require__(6928);
5848
+ const IGNORED_FILES = new Set([".DS_Store", "Thumbs.db"]);
5849
+ const isIgnoredFile = path => {
5850
+ var _a;
5851
+ return IGNORED_FILES.has((_a = path.split("/").pop()) !== null && _a !== void 0 ? _a : "");
5852
+ };
5853
+ function listFilesRecursive(dir) {
5854
+ const resolved = (0, path_1.resolve)(dir);
5855
+ const entries = (0, fs_1.readdirSync)(resolved, {
5856
+ recursive: true,
5857
+ encoding: "utf-8"
5858
+ });
5859
+ return entries.filter(entry => {
5860
+ if (isIgnoredFile(entry)) {
5861
+ // OS junk files exist locally but not in CI checkouts — they
5862
+ // must never influence the content hash
5863
+ return false;
5864
+ }
5865
+ const fullPath = (0, path_1.join)(resolved, entry);
5866
+ return (0, fs_1.statSync)(fullPath).isFile();
5867
+ }).sort();
5868
+ }
5869
+ function computeCustomImageHash(config) {
5870
+ var _a;
5871
+ const hash = (0, crypto_1.createHash)("sha256");
5872
+ const watchedPaths = [];
5873
+ // Hash all files in the dir (includes the Dockerfile and any context files)
5874
+ const dirFiles = listFilesRecursive(config.dir);
5875
+ for (const file of dirFiles) {
5876
+ const fullPath = (0, path_1.join)((0, path_1.resolve)(config.dir), file);
5877
+ hash.update(file); // include relative path for disambiguation
5878
+ hash.update((0, fs_1.readFileSync)(fullPath));
5879
+ }
5880
+ watchedPaths.push(`${config.dir}/**/*`);
5881
+ // extra dirs (e.g. included base image definitions)
5882
+ for (const extraDir of (_a = config.extraDirs) !== null && _a !== void 0 ? _a : []) {
5883
+ for (const file of listFilesRecursive(extraDir)) {
5884
+ hash.update(file);
5885
+ hash.update((0, fs_1.readFileSync)((0, path_1.join)((0, path_1.resolve)(extraDir), file)));
5886
+ }
5887
+ watchedPaths.push(`${extraDir}/**/*`);
5888
+ }
5889
+ // Hash extra paths (for files outside the dir)
5890
+ if (config.hashExtraPaths) {
5891
+ // Extra paths are literal file paths (not globs) for simplicity
5892
+ const extraFiles = [...config.hashExtraPaths].sort();
5893
+ for (const file of extraFiles) {
5894
+ hash.update(file);
5895
+ hash.update((0, fs_1.readFileSync)(file));
5896
+ }
5897
+ watchedPaths.push(...config.hashExtraPaths);
5898
+ }
5899
+ // Hash build args (sorted keys for determinism)
5900
+ if (config.buildArgs) {
5901
+ const sorted = Object.entries(config.buildArgs).sort(([a], [b]) => a.localeCompare(b));
5902
+ hash.update(JSON.stringify(sorted));
5903
+ }
5904
+ return {
5905
+ hash: hash.digest("hex").slice(0, 12),
5906
+ watchedPaths
5907
+ };
5908
+ }
5909
+ exports.computeCustomImageHash = computeCustomImageHash;
5910
+
5911
+ /***/ }),
5912
+
5913
+ /***/ 1585:
5914
+ /***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
5915
+
5916
+ "use strict";
5917
+
5918
+
5919
+ Object.defineProperty(exports, "__esModule", ({
5920
+ value: true
5921
+ }));
5922
+ exports.JobImagesPlan = exports.getJobImagesMode = exports.GENERATED_IMAGES_FOLDER = void 0;
5923
+ const fs_1 = __nccwpck_require__(9896);
5924
+ const path_1 = __nccwpck_require__(6928);
5925
+ const ciVariables_1 = __nccwpck_require__(4489);
5926
+ const docker_1 = __nccwpck_require__(2764);
5927
+ const constants_1 = __nccwpck_require__(3480);
5928
+ const runner_1 = __nccwpck_require__(2348);
5929
+ const gitlab_1 = __nccwpck_require__(3468);
5930
+ const hash_1 = __nccwpck_require__(3231);
5931
+ const shippedImages_1 = __nccwpck_require__(7734);
5932
+ exports.GENERATED_IMAGES_FOLDER = ".catladder-generated/images";
5933
+ /**
5934
+ * the official docker cli image used to build the job images — NOT a
5935
+ * catladder image, so building images never depends on one (bootstrap)
5936
+ */
5937
+ const BUILDER_IMAGE = "docker.io/docker:29.5.1";
5938
+ const getJobImagesMode = (config, pipelineType) => {
5939
+ var _a;
5940
+ // the central registry is not reachable from github runners
5941
+ if (pipelineType === "github" && config.jobImages === "central") {
5942
+ throw new Error(`jobImages: "central" is not supported on github: catladder's central registry (${constants_1.DOCKER_REGISTRY}) is unreachable from github runners. Use "repo" (the default) instead.`);
5943
+ }
5944
+ return (_a = config.jobImages) !== null && _a !== void 0 ? _a : pipelineType === "github" ? "repo" : "central";
5945
+ };
5946
+ exports.getJobImagesMode = getJobImagesMode;
5947
+ const ensureJobName = name => `🐳 job image ${name}`;
5948
+ /**
5949
+ * plans the catladder job images of one backend run: resolves image
5950
+ * references, and (in repo mode) collects the image build jobs and the
5951
+ * image definitions to materialize into the generated files.
5952
+ *
5953
+ * Reference-driven: only images actually used by a job are built.
5954
+ */
5955
+ class JobImagesPlan {
5956
+ constructor(mode, pipelineType) {
5957
+ this.mode = mode;
5958
+ this.pipelineType = pipelineType;
5959
+ this.used = new Map();
5960
+ }
5961
+ /**
5962
+ * resolves a job image (marker or concrete) to a concrete image url
5963
+ * plus the build job dependency in repo mode
5964
+ */
5965
+ resolve(image) {
5966
+ if (!(0, runner_1.isCatladderImageRef)(image)) {
5967
+ return {
5968
+ image
5969
+ };
5970
+ }
5971
+ return this.resolveRef(image);
5972
+ }
5973
+ resolveRef(ref) {
5974
+ const name = ref.catladderImage;
5975
+ if (this.mode === "central" || shippedImages_1.CENTRAL_ONLY_IMAGES.has(name)) {
5976
+ return {
5977
+ image: (0, runner_1.getCentralRunnerImageUrl)(name)
5978
+ };
5979
+ }
5980
+ const {
5981
+ hash
5982
+ } = this.use(name);
5983
+ return {
5984
+ image: `${this.registryImageRef()}/catladder/${name}:${hash}`,
5985
+ fromRepoRegistry: true,
5986
+ need: {
5987
+ job: ensureJobName(name),
5988
+ artifacts: false,
5989
+ optional: true,
5990
+ global: true
5991
+ }
5992
+ };
5993
+ }
5994
+ /**
5995
+ * the registry image prefix. On github, container images can't use
5996
+ * runtime env vars — the expression form is required.
5997
+ */
5998
+ registryImageRef() {
5999
+ return this.pipelineType === "github" ? "ghcr.io/${{ github.repository }}" : `${(0, ciVariables_1.getCiVariable)({
6000
+ pipelineType: this.pipelineType
6001
+ }, "registryImage")}`;
6002
+ }
6003
+ use(name) {
6004
+ var _a;
6005
+ const existing = this.used.get(name);
6006
+ if (existing) {
6007
+ return existing;
6008
+ }
6009
+ const dependencies = (_a = shippedImages_1.RUNNER_IMAGE_DEPENDENCIES[name]) !== null && _a !== void 0 ? _a : [];
6010
+ // dependencies are materialized (and hashed) as part of this image,
6011
+ // but also usable standalone
6012
+ dependencies.forEach(dependency => this.use(dependency));
6013
+ const {
6014
+ hash
6015
+ } = (0, hash_1.computeCustomImageHash)({
6016
+ dir: (0, shippedImages_1.getShippedImageDir)(name),
6017
+ extraDirs: dependencies.map(dependency => (0, shippedImages_1.getShippedImageDir)(dependency))
6018
+ });
6019
+ const entry = {
6020
+ hash
6021
+ };
6022
+ this.used.set(name, entry);
6023
+ return entry;
6024
+ }
6025
+ /**
6026
+ * the image build jobs for all used images (repo mode; empty in
6027
+ * central mode)
6028
+ */
6029
+ getEnsureJobs() {
6030
+ return [...this.used.entries()].map(([name, {
6031
+ hash
6032
+ }]) => this.createEnsureJob(name, hash));
6033
+ }
6034
+ createEnsureJob(name, hash) {
6035
+ var _a;
6036
+ const ctx = {
6037
+ pipelineType: this.pipelineType
6038
+ };
6039
+ const registryUser = (0, ciVariables_1.getCiVariable)(ctx, "registryUser");
6040
+ const jobToken = (0, ciVariables_1.getCiVariable)(ctx, "jobToken");
6041
+ const registry = (0, ciVariables_1.getCiVariable)(ctx, "registry");
6042
+ const imageRef = `${this.registryImageRef()}/catladder/${name}:${hash}`;
6043
+ const watchedDirs = [name, ...((_a = shippedImages_1.RUNNER_IMAGE_DEPENDENCIES[name]) !== null && _a !== void 0 ? _a : [])];
6044
+ const {
6045
+ services,
6046
+ runnerVariables
6047
+ } = (0, docker_1.getDockerJobBaseProps)();
6048
+ return {
6049
+ name: ensureJobName(name),
6050
+ stage: "setup",
6051
+ image: BUILDER_IMAGE,
6052
+ services,
6053
+ runnerVariables,
6054
+ needs: [],
6055
+ // gitlab: only run when the image definition changed; github has
6056
+ // no equivalent and always runs (the existence check is fast)
6057
+ rules: [{
6058
+ changes: watchedDirs.map(dir => `${exports.GENERATED_IMAGES_FOLDER}/${dir}/**/*`)
6059
+ }],
6060
+ variables: {},
6061
+ script: [...(0, gitlab_1.collapseableSection)("docker-login", "Docker Login")([`docker login --username ${registryUser} --password ${jobToken} ${registry}`]), ...(0, gitlab_1.collapseableSection)("check-image", "Checking if image exists")([`if docker manifest inspect ${imageRef} > /dev/null 2>&1; then`, ` echo "Image ${imageRef} already exists, skipping build"`, ` exit 0`, `fi`]), ...(0, gitlab_1.collapseableSection)("docker-build", "Building image")([`DOCKER_BUILDKIT=1 docker build -t ${imageRef} -f ${exports.GENERATED_IMAGES_FOLDER}/${name}/Dockerfile ${shippedImages_1.RUNNER_IMAGE_BUILD_CONTEXT[name] === "root" ? exports.GENERATED_IMAGES_FOLDER : `${exports.GENERATED_IMAGES_FOLDER}/${name}`}`]), ...(0, gitlab_1.collapseableSection)("docker-push", "Pushing image")([`docker push ${imageRef}`])]
6062
+ };
6063
+ }
6064
+ /**
6065
+ * the image definitions of all used images, materialized into the
6066
+ * generated files (so pipelines build from committed, reviewable
6067
+ * files and gitlab's rules:changes can watch them)
6068
+ */
6069
+ getGeneratedFiles() {
6070
+ return [...this.used.keys()].flatMap(name => {
6071
+ const dir = (0, shippedImages_1.getShippedImageDir)(name);
6072
+ return listFilesRecursive(dir).map(file => ({
6073
+ path: (0, path_1.join)(exports.GENERATED_IMAGES_FOLDER, name, file),
6074
+ content: (0, fs_1.readFileSync)((0, path_1.join)(dir, file), "utf-8")
6075
+ }));
6076
+ });
6077
+ }
6078
+ }
6079
+ exports.JobImagesPlan = JobImagesPlan;
6080
+ const IGNORED_FILES = new Set([".DS_Store", "Thumbs.db"]);
6081
+ const listFilesRecursive = dir => (0, fs_1.readdirSync)(dir, {
6082
+ recursive: true,
6083
+ encoding: "utf-8"
6084
+ }).filter(entry => {
6085
+ var _a;
6086
+ // OS junk files exist locally but not in CI checkouts
6087
+ return !IGNORED_FILES.has((_a = entry.split("/").pop()) !== null && _a !== void 0 ? _a : "") && (0, fs_1.statSync)((0, path_1.join)(dir, entry)).isFile();
6088
+ }).sort();
6089
+
6090
+ /***/ }),
6091
+
6092
+ /***/ 7734:
6093
+ /***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
6094
+
6095
+ "use strict";
6096
+
6097
+
6098
+ Object.defineProperty(exports, "__esModule", ({
6099
+ value: true
6100
+ }));
6101
+ exports.getShippedImageDir = exports.getShippedImagesDir = exports.RUNNER_IMAGE_BUILD_CONTEXT = exports.CENTRAL_ONLY_IMAGES = exports.RUNNER_IMAGE_DEPENDENCIES = void 0;
6102
+ const fs_1 = __nccwpck_require__(9896);
6103
+ const path_1 = __nccwpck_require__(6928);
6104
+ /**
6105
+ * images whose Dockerfile INCLUDEs another image's Dockerfile
6106
+ * (dockerfile-x/dockerfile-plus, resolved against the images root as
6107
+ * build context)
6108
+ */
6109
+ exports.RUNNER_IMAGE_DEPENDENCIES = {
6110
+ "jobs-meteor": ["jobs-default"],
6111
+ "jobs-testing-chrome": ["jobs-default"]
6112
+ };
6113
+ /**
6114
+ * the docker build context of each image, mirroring the central image
6115
+ * builds: the jobs family builds from the images root (their
6116
+ * Dockerfiles INCLUDE sibling images), the others from their own dir
6117
+ * (their COPY paths are relative to it)
6118
+ */
6119
+ /**
6120
+ * images that cannot be built from the shipped definitions and always
6121
+ * come from the central registry: semantic-release bakes catladder's
6122
+ * own cli sources into the image (repo-root build context).
6123
+ * TODO: make the semantic-release image self-contained.
6124
+ */
6125
+ exports.CENTRAL_ONLY_IMAGES = new Set(["semantic-release"]);
6126
+ exports.RUNNER_IMAGE_BUILD_CONTEXT = {
6127
+ "jobs-default": "root",
6128
+ "jobs-meteor": "root",
6129
+ "jobs-testing-chrome": "root",
6130
+ kubernetes: "self",
6131
+ "docker-build": "self",
6132
+ gcloud: "self",
6133
+ "semantic-release": "self",
6134
+ "agent-claude": "self"
6135
+ };
6136
+ /**
6137
+ * the directory containing the image definitions shipped with the
6138
+ * package (copied into dist at build time; the repository layout is
6139
+ * the fallback for development and tests)
6140
+ */
6141
+ const getShippedImagesDir = () => {
6142
+ const candidates = [
6143
+ // built package: dist/customImages -> dist/runner-images
6144
+ (0, path_1.join)(__dirname, "..", "runner-images"),
6145
+ // repository: pipeline/src/customImages -> <root>/runner-images
6146
+ (0, path_1.join)(__dirname, "..", "..", "..", "runner-images")];
6147
+ const found = candidates.find(candidate => (0, fs_1.existsSync)(candidate));
6148
+ if (!found) {
6149
+ throw new Error("shipped job image definitions not found");
6150
+ }
6151
+ return found;
6152
+ };
6153
+ exports.getShippedImagesDir = getShippedImagesDir;
6154
+ const getShippedImageDir = name => (0, path_1.join)((0, exports.getShippedImagesDir)(), name);
6155
+ exports.getShippedImageDir = getShippedImageDir;
6156
+
6157
+ /***/ }),
6158
+
5529
6159
  /***/ 4628:
5530
6160
  /***/ ((__unused_webpack_module, exports) => {
5531
6161
 
@@ -6752,7 +7382,10 @@ exports.GCLOUD_RUN_DEPLOY_TYPE = {
6752
7382
  hidden: true
6753
7383
  }, {
6754
7384
  key: exports.GCLOUD_RUN_CANONICAL_HOST_SUFFIX,
6755
- hidden: true
7385
+ hidden: true,
7386
+ // a public url fragment, not sensitive: masking it would
7387
+ // suppress environment urls on github and break log links
7388
+ kind: "variable"
6756
7389
  }] : []), ...(ctx.deployConfigRaw && ctx.deployConfigRaw.cloudSql ? [{
6757
7390
  key: "DB_PASSWORD"
6758
7391
  }] : [])],
@@ -6868,7 +7501,10 @@ const getDatabaseDeleteScript = (context, deployConfig) => {
6868
7501
  // unfortunatly the database cannot be deleted right after the service has stopped, as it might still have connections
6869
7502
  // we have to repeate this process
6870
7503
  return [`echo "deleting database ${DB_NAME}..."`, `echo "👆 this can take multiple attemps (3-5min), because google cloud run may still have a connection to the database after the cloud run service is shut down"`, (0, gitlab_1.repeatOnFailure)(`gcloud sql databases delete ${DB_NAME} --instance=${instanceId} --project ${projectId}`, {
6871
- pauseInSeconds: 10
7504
+ pauseInSeconds: 10,
7505
+ // connections normally clear within 3-5min; anything beyond is
7506
+ // a permanent error (missing permission, wrong instance, ...)
7507
+ maxAttempts: 30
6872
7508
  })];
6873
7509
  };
6874
7510
  exports.getDatabaseDeleteScript = getDatabaseDeleteScript;
@@ -8592,6 +9228,7 @@ const promises_1 = __nccwpck_require__(1943);
8592
9228
  const path_1 = __nccwpck_require__(6928);
8593
9229
  const backends_1 = __nccwpck_require__(3243);
8594
9230
  const gitlab_1 = __nccwpck_require__(8475);
9231
+ const jobImagesPlan_1 = __nccwpck_require__(1585);
8595
9232
  /**
8596
9233
  * generates the pipeline files of all pipeline types enabled in the
8597
9234
  * config (see `pipelines`). Pass an explicit `pipelineType` to generate
@@ -8599,6 +9236,16 @@ const gitlab_1 = __nccwpck_require__(8475);
8599
9236
  */
8600
9237
  async function generatePipelineFiles(context, pipelineType) {
8601
9238
  const pipelineTypes = pipelineType ? [pipelineType] : (0, backends_1.getEnabledPipelineTypes)(context.config);
9239
+ if (!pipelineType) {
9240
+ // the materialized image definitions are a union across all enabled
9241
+ // backends and no single backend may wipe them — cleaned once per
9242
+ // full generation, so images that fell out of use don't linger (and
9243
+ // keep triggering gitlab's rules:changes)
9244
+ await (0, promises_1.rm)(jobImagesPlan_1.GENERATED_IMAGES_FOLDER, {
9245
+ recursive: true,
9246
+ force: true
9247
+ });
9248
+ }
8602
9249
  for (const type of pipelineTypes) {
8603
9250
  await generatePipelineFilesForBackend(context, (0, backends_1.getPipelineBackend)(type));
8604
9251
  }
@@ -8616,7 +9263,11 @@ async function generatePipelineFilesForBackend(context, backend) {
8616
9263
  await (0, promises_1.mkdir)((0, path_1.dirname)(path), {
8617
9264
  recursive: true
8618
9265
  });
8619
- await context.fileWriter.writeYamlfile(path, content);
9266
+ if (typeof content === "string") {
9267
+ await context.fileWriter.writeGeneratedFileRaw(path, content);
9268
+ } else {
9269
+ await context.fileWriter.writeYamlfile(path, content);
9270
+ }
8620
9271
  }));
8621
9272
  }
8622
9273
  /**
@@ -8972,10 +9623,23 @@ exports.RULE_CONDITION_HOTFIX_BRANCH = "$CI_COMMIT_BRANCH =~ /^[0-9]+.([0-9]+|x)
8972
9623
  Object.defineProperty(exports, "__esModule", ({
8973
9624
  value: true
8974
9625
  }));
8975
- exports.getRunnerImage = void 0;
9626
+ exports.getCentralRunnerImageUrl = exports.getRunnerImage = exports.isCatladderImageRef = void 0;
8976
9627
  const constants_1 = __nccwpck_require__(3480);
8977
- const getRunnerImage = imageName => constants_1.DOCKER_REGISTRY + "/" + imageName + ":" + constants_1.PIPELINE_IMAGE_TAG;
9628
+ const isCatladderImageRef = value => typeof value === "object" && value !== null && "catladderImage" in value && typeof value.catladderImage === "string";
9629
+ exports.isCatladderImageRef = isCatladderImageRef;
9630
+ /**
9631
+ * job creators use this to reference a catladder-provided job image;
9632
+ * the backends resolve it to a concrete image url
9633
+ */
9634
+ const getRunnerImage = imageName => ({
9635
+ catladderImage: imageName
9636
+ });
8978
9637
  exports.getRunnerImage = getRunnerImage;
9638
+ /**
9639
+ * the image url in catladder's central registry (the classic behavior)
9640
+ */
9641
+ const getCentralRunnerImageUrl = imageName => constants_1.DOCKER_REGISTRY + "/" + imageName + ":" + constants_1.PIPELINE_IMAGE_TAG;
9642
+ exports.getCentralRunnerImageUrl = getCentralRunnerImageUrl;
8979
9643
 
8980
9644
  /***/ }),
8981
9645
 
@@ -9190,8 +9854,14 @@ const sanitizeForBashVariable = name => name.replace(/-/g, "_");
9190
9854
  exports.sanitizeForBashVariable = sanitizeForBashVariable;
9191
9855
  const repeatOnFailure = (command, options) => {
9192
9856
  return `
9857
+ _attempt=0
9193
9858
  until ${command}
9194
9859
  do
9860
+ _attempt=$((_attempt+1))
9861
+ if [ "$_attempt" -ge ${options.maxAttempts} ]; then
9862
+ echo "giving up after ${options.maxAttempts} attempts"
9863
+ exit 1
9864
+ fi
9195
9865
  echo "Trying again."
9196
9866
  sleep ${options.pauseInSeconds}
9197
9867
  done
@@ -9379,6 +10049,14 @@ class FileWriter {
9379
10049
  extension: (_b = path.split(".").pop()) !== null && _b !== void 0 ? _b : ""
9380
10050
  };
9381
10051
  }
10052
+ /**
10053
+ * writes content verbatim (no auto-generated header) — e.g. for
10054
+ * materialized dockerfiles, where a leading comment would break
10055
+ * parser directives like `# syntax = ...`
10056
+ */
10057
+ async writeGeneratedFileRaw(path, content) {
10058
+ await this.writeTheFile(path, content);
10059
+ }
9382
10060
  async writeGeneratedFile(path, content, options) {
9383
10061
  await this.writeTheFile(path, [this.getAutoGeneratedHeader(options.commentChar), content].join("\n"));
9384
10062
  }