@catladder/pipeline 1.101.4 → 1.103.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/dist/build/base/createBuildJob.js +3 -2
  2. package/dist/build/base/writeDotEnv.d.ts +7 -0
  3. package/dist/build/base/writeDotEnv.js +43 -0
  4. package/dist/build/custom/testJob.js +2 -2
  5. package/dist/build/node/testJob.js +2 -2
  6. package/dist/build/rails/build.js +11 -1
  7. package/dist/build/rails/test.js +1 -1
  8. package/dist/build/types.d.ts +8 -1
  9. package/dist/bundles/catladder-gitlab/index.js +3 -3
  10. package/dist/constants.js +1 -1
  11. package/dist/context/getEnvironment.js +18 -12
  12. package/dist/context/getEnvironmentVariables.d.ts +2 -7
  13. package/dist/context/getEnvironmentVariables.js +27 -31
  14. package/dist/context/transformJobOnlyVars.d.ts +10 -0
  15. package/dist/context/transformJobOnlyVars.js +160 -0
  16. package/dist/context/utils/envVars.d.ts +8 -0
  17. package/dist/context/utils/envVars.js +52 -0
  18. package/dist/deploy/base.js +3 -3
  19. package/dist/deploy/types/base.d.ts +8 -0
  20. package/dist/tsconfig.tsbuildinfo +1 -1
  21. package/dist/types/config.d.ts +6 -0
  22. package/dist/types/context.d.ts +16 -6
  23. package/examples/__snapshots__/custom-deploy.ts.snap +8 -0
  24. package/examples/__snapshots__/native-app.ts.snap +2958 -0
  25. package/examples/custom-deploy.ts +3 -0
  26. package/examples/native-app.ts +81 -0
  27. package/package.json +1 -1
  28. package/src/build/base/createBuildJob.ts +3 -0
  29. package/src/build/base/writeDotEnv.ts +21 -0
  30. package/src/build/custom/testJob.ts +1 -0
  31. package/src/build/node/testJob.ts +1 -0
  32. package/src/build/rails/build.ts +4 -1
  33. package/src/build/rails/test.ts +4 -1
  34. package/src/build/types.ts +9 -1
  35. package/src/context/getEnvironment.ts +9 -7
  36. package/src/context/getEnvironmentVariables.ts +36 -24
  37. package/src/context/transformJobOnlyVars.ts +42 -0
  38. package/src/context/utils/envVars.ts +26 -0
  39. package/src/deploy/base.ts +3 -0
  40. package/src/deploy/types/base.ts +10 -0
  41. package/src/types/config.ts +7 -0
  42. package/src/types/context.ts +18 -6
  43. package/tsconfig.json +1 -1
@@ -13,6 +13,9 @@ const config: Config = {
13
13
  deploy: {
14
14
  type: "custom",
15
15
  requiresDocker: true,
16
+ jobVars: {
17
+ secret: ["DEPLOY_API_KEY"],
18
+ },
16
19
  script: ["echo 'would deploy'"],
17
20
  stopScript: ["echo 'would stop'"],
18
21
  },
@@ -0,0 +1,81 @@
1
+ import type { Config } from "../src";
2
+ import type { CatladderJob } from "../src/types/jobs";
3
+ import { createAllPipelines } from "./__utils__/helpers";
4
+ const APP_GEM_CACHE: CatladderJob["cache"] = [
5
+ {
6
+ key: {
7
+ files: ["app/Gemfile.lock"],
8
+ },
9
+ paths: ["app/vendor"],
10
+ },
11
+ ];
12
+
13
+ const config: Config = {
14
+ appName: "test-app",
15
+ customerName: "pan",
16
+ components: {
17
+ app: {
18
+ dir: "app",
19
+ dotEnv: true,
20
+ vars: {
21
+ public: {
22
+ GRAPHQL_URL: "${api:ROOT_URL}/graphql",
23
+ },
24
+ secret: [
25
+ "APP_STORE_CONNECT_API_KEY_CONTENT",
26
+ "APP_STORE_CONNECT_ISSUER_ID",
27
+ "APP_STORE_CONNECT_API_KEY_ID",
28
+ ],
29
+ },
30
+ build: {
31
+ extraVars: {
32
+ LC_A: "L=en_US.UTF-8",
33
+ LANG: "en_US.UTF-8",
34
+ },
35
+ type: "node",
36
+ buildCommand: [
37
+ "bundle config set --local path 'vendor/ruby'",
38
+ "gem install bundler",
39
+ "bundle install",
40
+ "bundle exec pod install --project-directory=ios",
41
+ "bundle exec fastlane build",
42
+ ],
43
+ jobTags: ["mac-runner"],
44
+ jobCache: APP_GEM_CACHE,
45
+ },
46
+
47
+ deploy: {
48
+ type: "custom",
49
+ extraVars: {
50
+ LC_A: "L=en_US.UTF-8",
51
+ LANG: "en_US.UTF-8",
52
+ },
53
+ requiresDocker: false,
54
+ script: [
55
+ "bundle config set --local path 'vendor/ruby'",
56
+ "gem install bundler",
57
+ "bundle install",
58
+ "bundle exec fastlane deploy_test",
59
+ ],
60
+ jobTags: ["mac-runner"],
61
+ jobCache: APP_GEM_CACHE,
62
+ },
63
+ },
64
+
65
+ api: {
66
+ dir: "api",
67
+ build: {
68
+ type: "node",
69
+ },
70
+ deploy: {
71
+ type: "google-cloudrun",
72
+ projectId: "asdf",
73
+ region: "asia-east1",
74
+ },
75
+ },
76
+ },
77
+ };
78
+
79
+ it("matches snapshot", async () => {
80
+ expect(await createAllPipelines(config)).toMatchSnapshot();
81
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@catladder/pipeline",
3
- "version": "1.101.4",
3
+ "version": "1.103.0",
4
4
  "scripts": {
5
5
  "build:tsc": "yarn tsc",
6
6
  "build": "yarn build:compile && yarn build:inline-variables && yarn build:bundle",
@@ -10,6 +10,7 @@ import {
10
10
  RUNNER_BUILD_RESOURCE_VARIABLES,
11
11
  } from "./constants";
12
12
  import { writeBuildInfo } from "./writeBuildInfo";
13
+ import { writeDotEnv } from "./writeDotEnv";
13
14
 
14
15
  export const createBuildJob = (
15
16
  context: Context,
@@ -27,10 +28,12 @@ export const createBuildJob = (
27
28
  ...RUNNER_BUILD_RESOURCE_VARIABLES,
28
29
  ...(variables ?? {}),
29
30
  ...context.environment.envVars,
31
+ ...context.environment.jobOnlyVars.build.envVars,
30
32
  ...(context.componentConfig.build.extraVars ?? {}),
31
33
  },
32
34
 
33
35
  script: [
36
+ ...(context.componentConfig.dotEnv ? writeDotEnv(context) : []),
34
37
  ...writeBuildInfo(context),
35
38
  ...ensureNodeVersion(context), // in pure node repos, we might want to have the nvmrc file in top-level
36
39
  `cd ${context.componentConfig.dir}`,
@@ -0,0 +1,21 @@
1
+ import type { Context } from "../../types";
2
+
3
+ /**
4
+ * writes a .env file in the components folder
5
+ * @param context
6
+ * @returns
7
+ */
8
+ export const writeDotEnv = (context: Context) => {
9
+ const envVars = context.environment.envVars;
10
+
11
+ // make key=value and sanitize multiline
12
+ const keyValueString = Object.entries(envVars)
13
+ .map(([key, value]) => `${key}=${value.replaceAll("\n", "\\n")}`)
14
+ .join("\n");
15
+
16
+ return [
17
+ `cat <<EOF > ${context.componentConfig.dir}/.env
18
+ ${keyValueString}
19
+ EOF`,
20
+ ];
21
+ };
@@ -29,6 +29,7 @@ export const createCustomTestJobs = (context: Context): CatladderJob[] => {
29
29
  variables: {
30
30
  APP_PATH: context.componentConfig.dir,
31
31
  ...RUNNER_CUSTOM_TEST_VARIABLES,
32
+ ...context.environment.jobOnlyVars.build.envVars,
32
33
  ...(buildConfig.extraVars ?? {}),
33
34
  },
34
35
  services: buildConfig.jobServices,
@@ -18,6 +18,7 @@ export const createNodeTestJobs = (context: Context): CatladderJob[] => {
18
18
  variables: {
19
19
  APP_PATH: context.componentConfig.dir,
20
20
  ...NODE_RUNNER_BUILD_VARIABLES,
21
+ ...context.environment.jobOnlyVars.build.envVars,
21
22
  ...(buildConfig.extraVars ?? {}),
22
23
  },
23
24
  stage: "test",
@@ -18,7 +18,10 @@ export const createRailsBuildJobs = (context: Context): CatladderJob[] => {
18
18
 
19
19
  return [
20
20
  createDockerBuildJobBase(context, {
21
- variables: context.componentConfig.build.extraVars,
21
+ variables: {
22
+ ...context.environment.jobOnlyVars.build.envVars,
23
+ ...context.componentConfig.build.extraVars,
24
+ },
22
25
  // custom script
23
26
  script: [
24
27
  gitlabDockerLogin,
@@ -11,7 +11,10 @@ export const createRailsTestJobs = (context: Context): CatladderJob[] => {
11
11
  const buildConfig = context.componentConfig.build;
12
12
 
13
13
  const base: Omit<CatladderJob, "script" | "name"> = {
14
- variables: buildConfig.extraVars ?? {},
14
+ variables: {
15
+ ...context.environment.jobOnlyVars.build.envVars,
16
+ ...(buildConfig.extraVars ?? {}),
17
+ },
15
18
  stage: "test",
16
19
  needs: [],
17
20
  envMode: "none",
@@ -1,4 +1,4 @@
1
- import type { GitlabJobImage, GitlabJobService } from "../types";
1
+ import type { EnvVars, GitlabJobImage, GitlabJobService } from "../types";
2
2
 
3
3
  import type { CatladderJob } from "../types/jobs";
4
4
 
@@ -17,8 +17,16 @@ export type BuildConfigBase = {
17
17
  * command to run on the image to start the app (e.g. yarn start)
18
18
  */
19
19
  startCommand?: string;
20
+
21
+ /**
22
+ * vars that only exist in the build jobs.
23
+ * Can curently not reference other variables from other components
24
+ */
25
+ jobVars?: EnvVars;
20
26
  /**
21
27
  * additional env vars for the buid jobs
28
+ *
29
+ * @deprecated use jobVars
22
30
  */
23
31
  extraVars?: Record<string, string>;
24
32
  /**
@@ -10,8 +10,12 @@ export const getEnvironment = async (
10
10
  env: string,
11
11
  commitInfo?: CommitInfo
12
12
  ): Promise<Environment> => {
13
- const { envVars, secretEnvVarKeys, host, url } =
14
- await getEnvironmentVariables(config, componentName, env, commitInfo);
13
+ const variables = await getEnvironmentVariables(
14
+ config,
15
+ componentName,
16
+ env,
17
+ commitInfo
18
+ );
15
19
 
16
20
  const envContext = getEnvironmentContext(
17
21
  config,
@@ -24,17 +28,15 @@ export const getEnvironment = async (
24
28
 
25
29
  return {
26
30
  envType,
27
- host,
28
- url,
31
+
29
32
  gitlabEnvironment: {
30
33
  name: envContext.gitlabEnvironmentName,
31
- url,
34
+ url: variables.url,
32
35
  },
33
36
  fullName: envContext.fullName,
34
37
  slugPrefix: envContext.environmentSlugPrefix,
35
38
  slug: envContext.environmentSlug,
36
39
  shortName: env,
37
- envVars,
38
- secretEnvVarKeys,
40
+ ...variables,
39
41
  };
40
42
  };
@@ -1,6 +1,10 @@
1
- import { isObject, merge } from "lodash";
1
+ import { merge } from "lodash";
2
2
  import { DEPLOY_TYPES } from "../deploy";
3
- import type { CommitInfo, Context } from "../types";
3
+ import type {
4
+ CommitInfo,
5
+ Context,
6
+ EnvironmentEnvVarPart as EnvironmentVariables,
7
+ } from "../types";
4
8
  import type { Config, DevLocalEnvConfig } from "../types/config";
5
9
 
6
10
  import { getEnvironmentContext } from "./getEnvironmentContext";
@@ -8,6 +12,12 @@ import {
8
12
  resolveReferences,
9
13
  translateLegacyFromComponents,
10
14
  } from "./resolveReferences";
15
+ import {
16
+ stringListToSecreteEnvVarList,
17
+ makeSecretEnvVarMapping,
18
+ stringifyValues,
19
+ } from "./utils/envVars";
20
+ import { transformJobOnlyVars } from "./transformJobOnlyVars";
11
21
 
12
22
  export type SecretEnvVar = {
13
23
  key: string;
@@ -20,12 +30,7 @@ export const getEnvironmentVariables = async (
20
30
  env: string,
21
31
  commitInfo?: CommitInfo,
22
32
  alreadyVisited: Record<string, Record<string, boolean>> = {} // to prevent endless loop
23
- ): Promise<{
24
- envVars: Record<string, string>;
25
- secretEnvVarKeys: SecretEnvVar[];
26
- host: string;
27
- url: string;
28
- }> => {
33
+ ): Promise<EnvironmentVariables> => {
29
34
  const environmentContext = getEnvironmentContext(
30
35
  config,
31
36
  env,
@@ -33,7 +38,8 @@ export const getEnvironmentVariables = async (
33
38
  commitInfo
34
39
  );
35
40
 
36
- const { envConfigRaw, deployConfigRaw, envType } = environmentContext;
41
+ const { envConfigRaw, deployConfigRaw, buildConfigRaw, envType } =
42
+ environmentContext;
37
43
 
38
44
  const basePredefinedVariables = {
39
45
  ENV_SHORT: env,
@@ -90,31 +96,24 @@ export const getEnvironmentVariables = async (
90
96
  : [];
91
97
 
92
98
  const secretEnvVarKeys: SecretEnvVar[] = [
93
- ...(envConfigRaw.vars?.secret ?? []).map((key) => ({ key })),
99
+ ...stringListToSecreteEnvVarList(envConfigRaw.vars?.secret ?? []),
94
100
  ...additionalSecretKeys,
95
101
  ];
96
- const secretEnvVars = Object.fromEntries(
97
- secretEnvVarKeys.map(({ key }) => [
98
- key,
99
- `$${getSecretVarName(env, componentName, key)}`,
100
- ])
102
+ const secretEnvVars = makeSecretEnvVarMapping(
103
+ env,
104
+ componentName,
105
+ secretEnvVarKeys
101
106
  );
102
-
103
107
  // this is deprecated, we now support: $componentname:FOO
104
108
  const legacyFromComponents = envConfigRaw.vars?.fromComponents ?? {};
105
- const publicEnvVarsRawWithLegasyFromComponents = merge(
109
+ const publicEnvVarsRawWithLegacyFromComponents = merge(
106
110
  {},
107
111
  translateLegacyFromComponents(legacyFromComponents),
108
112
  publicEnvVarsRaw
109
113
  );
110
114
 
111
- const publicEnvVarsRawSanitized = Object.fromEntries(
112
- Object.entries(publicEnvVarsRawWithLegasyFromComponents).map(
113
- ([key, value]) => [
114
- key,
115
- isObject(value) ? JSON.stringify(value) : `${value}`,
116
- ]
117
- )
115
+ const publicEnvVarsRawSanitized = stringifyValues(
116
+ publicEnvVarsRawWithLegacyFromComponents
118
117
  );
119
118
 
120
119
  const envVarsRaw = addIndexVar({
@@ -141,6 +140,19 @@ export const getEnvironmentVariables = async (
141
140
  return {
142
141
  envVars,
143
142
  secretEnvVarKeys,
143
+ jobOnlyVars: {
144
+ build: await transformJobOnlyVars(
145
+ env,
146
+ componentName,
147
+ (buildConfigRaw && buildConfigRaw.jobVars) || null
148
+ ),
149
+ deploy: await transformJobOnlyVars(
150
+ env,
151
+ componentName,
152
+ (deployConfigRaw && deployConfigRaw.jobVars) || null
153
+ ),
154
+ },
155
+
144
156
  host,
145
157
  url,
146
158
  };
@@ -0,0 +1,42 @@
1
+ import type { EnvironmentEnvVars } from "..";
2
+ import type { EnvVars } from "../types/config";
3
+ import {
4
+ makeSecretEnvVarMapping,
5
+ stringListToSecreteEnvVarList,
6
+ stringifyValues,
7
+ } from "./utils/envVars";
8
+
9
+ /**
10
+ * transform EnvVars for environment.
11
+ *
12
+ * currently does not resolve references and is only used for additional job-only-vars
13
+ * @param vars
14
+ * @returns
15
+ */
16
+ export const transformJobOnlyVars = async (
17
+ env: string,
18
+ componentName: string,
19
+ vars: EnvVars | null
20
+ ): Promise<EnvironmentEnvVars> => {
21
+ if (!vars) {
22
+ return {
23
+ envVars: {},
24
+ secretEnvVarKeys: [],
25
+ };
26
+ }
27
+
28
+ const publicVars = stringifyValues(vars?.public ?? {});
29
+ const secretEnvVarKeys = stringListToSecreteEnvVarList(vars?.secret ?? []);
30
+ const deployJobOnlySecretEnvVars = vars?.secret
31
+ ? makeSecretEnvVarMapping(env, componentName, secretEnvVarKeys)
32
+ : {};
33
+
34
+ const envVars = {
35
+ ...deployJobOnlySecretEnvVars,
36
+ ...publicVars,
37
+ };
38
+ return {
39
+ envVars,
40
+ secretEnvVarKeys,
41
+ };
42
+ };
@@ -0,0 +1,26 @@
1
+ import { isObject } from "lodash";
2
+ import type { SecretEnvVar } from "../getEnvironmentVariables";
3
+ import { getSecretVarName } from "../getEnvironmentVariables";
4
+
5
+ export const stringifyValues = (obj: Record<string, unknown>) =>
6
+ Object.fromEntries(
7
+ Object.entries(obj).map(([key, value]) => [
8
+ key,
9
+ isObject(value) ? JSON.stringify(value) : `${value}`,
10
+ ])
11
+ );
12
+
13
+ export const stringListToSecreteEnvVarList = (keys: string[]): SecretEnvVar[] =>
14
+ keys.map((key) => ({ key }));
15
+ export const makeSecretEnvVarMapping = (
16
+ env: string,
17
+ componentName: string,
18
+ secretEnvVars: SecretEnvVar[]
19
+ ) => {
20
+ return Object.fromEntries(
21
+ secretEnvVars.map(({ key }) => [
22
+ key,
23
+ `$${getSecretVarName(env, componentName, key)}`,
24
+ ])
25
+ );
26
+ };
@@ -76,6 +76,7 @@ export const getBaseDeploymentJob = (context: Context): JobWithoutScript => {
76
76
  ...DEPLOY_RUNNER_VARIABLES,
77
77
  ...context.environment.envVars,
78
78
  ...(hasDocker ? getDockerImageVariables(context) : {}),
79
+ ...context.environment.jobOnlyVars.deploy.envVars,
79
80
  ...(context.componentConfig.deploy
80
81
  ? context.componentConfig.deploy.extraVars ?? {}
81
82
  : {}),
@@ -115,6 +116,7 @@ export const getBaseDeploymentStopJob = (
115
116
  ],
116
117
  variables: {
117
118
  ...DEPLOY_RUNNER_VARIABLES,
119
+ ...context.environment.jobOnlyVars.deploy.envVars,
118
120
  GIT_STRATEGY: "none",
119
121
  },
120
122
  stage: "stop",
@@ -138,6 +140,7 @@ export const getBaseRollbackJob = (context: Context): JobWithoutScript => {
138
140
  ],
139
141
  variables: {
140
142
  ...DEPLOY_RUNNER_VARIABLES,
143
+ ...context.environment.jobOnlyVars.deploy.envVars,
141
144
  GIT_STRATEGY: "none",
142
145
  },
143
146
  stage: "rollback",
@@ -1,3 +1,5 @@
1
+ import type { EnvVars } from "../../types/config";
2
+
1
3
  export type DeployConfigBase = {
2
4
  /**
3
5
  * whether to deploy automatically or manual. If not defined, these rules apply:
@@ -17,8 +19,16 @@ export type DeployConfigBase = {
17
19
  */
18
20
  jobTags?: string[];
19
21
 
22
+ /**
23
+ * vars that only exist in the deploy job.
24
+ * Can curently not reference other variables from other components
25
+ */
26
+ jobVars?: EnvVars;
27
+
20
28
  /**
21
29
  * additional env vars for the deploy job
30
+ *
31
+ * @deprecated use deploy.jobVars for deploy job specific variables
22
32
  */
23
33
  extraVars?: Record<string, string>;
24
34
  };
@@ -144,6 +144,13 @@ export type ComponentConfig<C extends ConfigProps = never> = {
144
144
  * This feedback will help us to generalize use cases
145
145
  */
146
146
  customJobs?: CatladderJob[] | ((context: Context) => CatladderJob[]);
147
+
148
+ /**
149
+ * experimental. whether to create a .env localy and during build jobs
150
+ *
151
+ * Be careful, this will overwrite any existing .env file!
152
+ */
153
+ dotEnv?: boolean;
147
154
  } & DefaultEnvConfig;
148
155
 
149
156
  export type ConfigProps = {
@@ -6,6 +6,22 @@ import type {
6
6
  EnvType,
7
7
  } from "./config";
8
8
 
9
+ export type EnvironmentEnvVars = {
10
+ envVars: Record<string, string>;
11
+ secretEnvVarKeys: SecretEnvVar[];
12
+ };
13
+ export type EnvironmentEnvVarPart = {
14
+ host: string;
15
+ url: string;
16
+
17
+ /**
18
+ * vars that only are injected in certain jobs, but not elsewhere
19
+ */
20
+ jobOnlyVars: {
21
+ deploy: EnvironmentEnvVars;
22
+ build: EnvironmentEnvVars;
23
+ };
24
+ } & EnvironmentEnvVars;
9
25
  export type Environment = {
10
26
  host: string;
11
27
  url: string;
@@ -21,13 +37,9 @@ export type Environment = {
21
37
  shortName: string;
22
38
  slugPrefix: string;
23
39
  slug: string;
24
- /**
25
- * env vars contain all build-time env vars. secrets have to be resolved (they are stored in gitlab)
26
- */
27
- envVars: Record<string, string>;
40
+
28
41
  envType: EnvType;
29
- secretEnvVarKeys: SecretEnvVar[];
30
- };
42
+ } & EnvironmentEnvVarPart;
31
43
 
32
44
  export type CommitInfo = {
33
45
  refName: string;
package/tsconfig.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "compilerOptions": {
3
- "lib": ["es2020", "DOM"],
3
+ "lib": ["es2021", "DOM"],
4
4
  "strict": true,
5
5
  "module": "CommonJS",
6
6
  "downlevelIteration": true,