polyci 0.0.6 → 0.0.8

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 (2) hide show
  1. package/dist/main.js +91 -48
  2. package/package.json +1 -1
package/dist/main.js CHANGED
@@ -4,24 +4,65 @@ import * as path from "node:path";
4
4
  import pino from "pino";
5
5
  import pretty from "pino-pretty";
6
6
  const log = pino(pretty());
7
- const DEFAULT_BRANCH_RULE = "/^(main)|(develop)|(release(-[a-z0-9]+)*)|(feature(-[a-z0-9]+)*)|(patch(-[a-z0-9]+)*)$/";
7
+ const DEFAULT_TAG_TEMPLATE = "{module}-v{version}";
8
+ const DEFAULT_BRANCH_TAG_TEMPLATE = "{module}-v{version}-{branch}-{increment}";
9
+ const ALLOWED_TAG_PLACEHOLDERS = new Set(["module", "branch", "version", "increment"]);
10
+ function applyTagTemplate(template, moduleName) {
11
+ return template
12
+ .replaceAll("{module}", moduleName)
13
+ .replaceAll("{branch}", "${CI_COMMIT_BRANCH}")
14
+ .replaceAll("{version}", "${NEXT_VERSION}")
15
+ .replaceAll("{increment}", "${NEXT_INCREMENT}");
16
+ }
17
+ function getInvalidTagTemplatePlaceholders(template) {
18
+ const matches = template.matchAll(/\{([^}]+)\}/g);
19
+ const invalid = new Set();
20
+ for (const match of matches) {
21
+ const placeholder = match[1];
22
+ if (!ALLOWED_TAG_PLACEHOLDERS.has(placeholder)) {
23
+ invalid.add(placeholder);
24
+ }
25
+ }
26
+ return [...invalid];
27
+ }
8
28
  function parseArgs() {
9
29
  const program = new Command();
10
30
  program
11
31
  .argument("[output]", "Output pipeline file path (or use --output)")
12
32
  .option("-o, --output <path>", "Output pipeline file path")
13
33
  .option("--modules-root <path>", "Modules root directory", "./modules")
14
- .option("--branch-rule <regex>", "Branch rule regex for module jobs", DEFAULT_BRANCH_RULE)
15
34
  .option("--main-branch <name>", "Primary branch name for release tagging logic", "main")
35
+ .option("--tag-template <template>", "Template for non-primary branch tags; use placeholders {module}, {branch}, {version}, {increment}", DEFAULT_TAG_TEMPLATE)
36
+ .option("--branch-tag-template <template>", "Template for non-primary branch tags; use placeholders {module}, {branch}, {version}, {increment}", DEFAULT_BRANCH_TAG_TEMPLATE)
16
37
  .option("--cwd <path>", "Working directory", process.cwd())
17
38
  .parse();
18
39
  const options = program.opts();
19
40
  const output = options.output ?? program.args[0] ?? "";
20
41
  const cwd = path.resolve(options.cwd);
21
42
  const modulesRoot = path.resolve(cwd, options.modulesRoot);
22
- const branchRule = options.branchRule;
23
43
  const mainBranch = options.mainBranch;
24
- return { cwd, output, modulesRoot, branchRule, mainBranch };
44
+ const tagTemplate = options.tagTemplate;
45
+ const branchTagTemplate = options.branchTagTemplate;
46
+ const invalidPrimary = getInvalidTagTemplatePlaceholders(tagTemplate);
47
+ if (invalidPrimary.length > 0) {
48
+ program.error(`Invalid --tag-template placeholders: ${invalidPrimary
49
+ .map((x) => `{${x}}`)
50
+ .join(", ")}. Allowed: {module}, {branch}, {version}, {increment}.`);
51
+ }
52
+ const invalidSecondary = getInvalidTagTemplatePlaceholders(branchTagTemplate);
53
+ if (invalidSecondary.length > 0) {
54
+ program.error(`Invalid --branch-tag-template placeholders: ${invalidSecondary
55
+ .map((x) => `{${x}}`)
56
+ .join(", ")}. Allowed: {module}, {branch}, {version}, {increment}.`);
57
+ }
58
+ return {
59
+ cwd,
60
+ output,
61
+ modulesRoot,
62
+ mainBranch,
63
+ tagTemplate,
64
+ branchTagTemplate,
65
+ };
25
66
  }
26
67
  function toPosixPath(p) {
27
68
  return p.split(path.sep).join("/");
@@ -109,7 +150,7 @@ function appendGlobalVariables(lines) {
109
150
  lines.push(" - deploy");
110
151
  lines.push("");
111
152
  }
112
- function appendModuleBuildJob(lines, module, branchRule) {
153
+ function appendModuleBuildJob(lines, module) {
113
154
  lines.push(`${module.jobId}_build:`);
114
155
  lines.push(" stage: build");
115
156
  lines.push(" rules:");
@@ -130,7 +171,7 @@ function appendModuleBuildJob(lines, module, branchRule) {
130
171
  lines.push(" expire_in: 1 day");
131
172
  lines.push("");
132
173
  }
133
- function appendModuleTestJob(lines, module, branchRule) {
174
+ function appendModuleTestJob(lines, module) {
134
175
  lines.push(`${module.jobId}_test:`);
135
176
  lines.push(" stage: test");
136
177
  lines.push(" rules:");
@@ -148,7 +189,7 @@ function appendModuleTestJob(lines, module, branchRule) {
148
189
  lines.push(' - echo "test is tasty"');
149
190
  lines.push("");
150
191
  }
151
- function appendModuleReleaseJob(lines, module, branchRule) {
192
+ function appendModuleReleaseJob(lines, module) {
152
193
  lines.push(`${module.jobId}_release:`);
153
194
  lines.push(" stage: release");
154
195
  lines.push(" rules:");
@@ -176,6 +217,7 @@ function appendModuleReleaseJob(lines, module, branchRule) {
176
217
  lines.push(` else`);
177
218
  lines.push(` PACKAGE_VERSION="$NEXT_VERSION-$CI_COMMIT_BRANCH-$NEXT_INCREMENT"`);
178
219
  lines.push(` fi`);
220
+ lines.push(" - |");
179
221
  lines.push(` if [ "$MODULE_TYPE" = "node-express" ]; then`);
180
222
  lines.push(` node -e "const fs=require('fs'); const pkg=JSON.parse(fs.readFileSync('package.json','utf8')); pkg.version=process.env.PACKAGE_VERSION; fs.writeFileSync('package.json', JSON.stringify(pkg, null, 2) + '\\n');"`);
181
223
  lines.push(` node ci/prepare-deploy-variables.js --instance-branch $CI_COMMIT_BRANCH`);
@@ -197,7 +239,7 @@ function appendModuleReleaseJob(lines, module, branchRule) {
197
239
  lines.push(` expire_in: 1 day`);
198
240
  lines.push("");
199
241
  }
200
- function appendGlobalReleaseJob(lines, modules, mainBranch) {
242
+ function appendGlobalReleaseJob(lines, modules, mainBranch, tagTemplate, branchTagTemplate) {
201
243
  lines.push("release_and_tag:");
202
244
  lines.push(" stage: release");
203
245
  lines.push(" needs:");
@@ -211,46 +253,47 @@ function appendGlobalReleaseJob(lines, modules, mainBranch) {
211
253
  lines.push(" script:");
212
254
  lines.push(" - apk update");
213
255
  lines.push(" - apk add $GIT_PACKAGE");
214
- lines.push(" - |");
215
- lines.push(" set -euo pipefail");
216
- lines.push(" git config user.email \"polyci@anarun.net\"");
217
- lines.push(" git config user.name \"polyci\"");
218
- lines.push(" git remote set-url origin \"https://oauth2:${GITLAB_TOKEN}@${CI_SERVER_HOST}/${CI_PROJECT_PATH}.git\"");
219
- lines.push(" TAG_CREATED=false");
256
+ lines.push(" - set -euo pipefail");
257
+ lines.push(" - git config user.email \"polyci@anarun.net\"");
258
+ lines.push(" - git config user.name \"polyci\"");
259
+ lines.push(" - git remote set-url origin \"https://oauth2:${GITLAB_TOKEN}@${CI_SERVER_HOST}/${CI_PROJECT_PATH}.git\"");
260
+ lines.push(" - TAG_CREATED=false");
220
261
  lines.push("");
221
262
  for (const module of modules) {
222
- lines.push(` echo "Tagging release for ${module.moduleName} (${module.modulePath})"`);
223
- lines.push(` if [ -f "${module.modulePath}/semalease.env" ]; then`);
224
- lines.push(` . "${module.modulePath}/semalease.env"`);
225
- lines.push(" TAG_NAME=\"\"");
226
- lines.push(` if [ "$CI_COMMIT_BRANCH" = "${mainBranch}" ]; then`);
227
- lines.push(" if [ \"${NEXT_VERSION:-}\" != \"${LATEST_VERSION:-}\" ]; then");
228
- lines.push(` TAG_NAME="${module.moduleName}-v\${NEXT_VERSION}"`);
229
- lines.push(" else");
230
- lines.push(` echo "Version did not change for ${module.moduleName} on branch $CI_COMMIT_BRANCH, skipping tag"`);
231
- lines.push(" fi");
263
+ lines.push(` - echo "Tagging release for ${module.moduleName} (${module.modulePath})"`);
264
+ lines.push(" - |");
265
+ lines.push(` if [ -f "${module.modulePath}/semalease.env" ]; then`);
266
+ lines.push(` . "${module.modulePath}/semalease.env"`);
267
+ lines.push(" TAG_NAME=\"\"");
268
+ lines.push(` if [ "$CI_COMMIT_BRANCH" = "${mainBranch}" ]; then`);
269
+ lines.push(" if [ \"${NEXT_VERSION:-}\" != \"${LATEST_VERSION:-}\" ]; then");
270
+ lines.push(` TAG_NAME="${applyTagTemplate(tagTemplate, module.moduleName)}"`);
232
271
  lines.push(" else");
233
- lines.push(" if [ \"${NEXT_VERSION:-}\" != \"${LATEST_VERSION:-}\" ] || [ \"${NEXT_INCREMENT:-}\" != \"${LATEST_INCREMENT:-}\" ]; then");
234
- lines.push(` TAG_NAME="${module.moduleName}-v\${NEXT_VERSION}-\${CI_COMMIT_BRANCH}-\${NEXT_INCREMENT}"`);
235
- lines.push(" else");
236
- lines.push(` echo "Version/increment did not change for ${module.moduleName} on branch $CI_COMMIT_BRANCH, skipping tag"`);
237
- lines.push(" fi");
272
+ lines.push(` echo "Version did not change for ${module.moduleName} on branch $CI_COMMIT_BRANCH, skipping tag"`);
238
273
  lines.push(" fi");
239
- lines.push(" if [ \"$TAG_NAME\" != \"\" ]; then");
274
+ lines.push(" else");
275
+ lines.push(" if [ \"${NEXT_VERSION:-}\" != \"${LATEST_VERSION:-}\" ] || [ \"${NEXT_INCREMENT:-}\" != \"${LATEST_INCREMENT:-}\" ]; then");
276
+ lines.push(` TAG_NAME="${applyTagTemplate(branchTagTemplate, module.moduleName)}"`);
277
+ lines.push(" else");
278
+ lines.push(` echo "Version/increment did not change for ${module.moduleName} on branch $CI_COMMIT_BRANCH, skipping tag"`);
279
+ lines.push(" fi");
280
+ lines.push(" fi");
281
+ lines.push(" if [ \"$TAG_NAME\" != \"\" ]; then");
240
282
  if (module.moduleType === "node-express") {
241
- lines.push(` git add "${module.modulePath}/package.json"`);
283
+ lines.push(` git add "${module.modulePath}/package.json"`);
242
284
  }
243
285
  else if (module.moduleType === "node-vite") {
244
- lines.push(` git add "${module.modulePath}/package.json" "${module.modulePath}/public/release.json"`);
286
+ lines.push(` git add "${module.modulePath}/package.json" "${module.modulePath}/public/release.json"`);
245
287
  }
246
- lines.push(" git tag \"$TAG_NAME\"");
247
- lines.push(" TAG_CREATED=true");
248
- lines.push(" fi");
249
- lines.push(" else");
250
- lines.push(` echo "Missing ${module.modulePath}/semalease.env, skipping ${module.moduleName}"`);
288
+ lines.push(" git tag \"$TAG_NAME\"");
289
+ lines.push(" TAG_CREATED=true");
251
290
  lines.push(" fi");
291
+ lines.push(" else");
292
+ lines.push(` echo "Missing ${module.modulePath}/semalease.env, skipping ${module.moduleName}"`);
293
+ lines.push(" fi");
252
294
  }
253
295
  lines.push("");
296
+ lines.push(" - |");
254
297
  lines.push(" if [ \"$TAG_CREATED\" = true ]; then");
255
298
  lines.push(" git commit -m \"release: update affected modules\"");
256
299
  lines.push(" git push origin HEAD");
@@ -267,7 +310,7 @@ function appendGlobalReleaseJob(lines, modules, mainBranch) {
267
310
  lines.push(` expire_in: 1 day`);
268
311
  lines.push("");
269
312
  }
270
- function appendModulePublishJob(lines, module, branchRule) {
313
+ function appendModulePublishJob(lines, module) {
271
314
  lines.push(`${module.jobId}_publish:`);
272
315
  lines.push(" stage: publish");
273
316
  lines.push(" rules:");
@@ -295,7 +338,7 @@ function appendModulePublishJob(lines, module, branchRule) {
295
338
  lines.push(" expire_in: 1 day");
296
339
  lines.push("");
297
340
  }
298
- function appendModuleDeployJob(lines, module, branchRule) {
341
+ function appendModuleDeployJob(lines, module) {
299
342
  lines.push(`${module.jobId}_deploy:`);
300
343
  lines.push(" stage: deploy");
301
344
  lines.push(" rules:");
@@ -326,23 +369,23 @@ function appendModuleDeployJob(lines, module, branchRule) {
326
369
  lines.push(" - rm -rf ~/.ssh");
327
370
  lines.push("");
328
371
  }
329
- function buildPipeline(modules, branchRule, mainBranch) {
372
+ function buildPipeline(modules, mainBranch, tagTemplate, branchTagTemplate) {
330
373
  const lines = [];
331
374
  appendGlobalVariables(lines);
332
375
  for (const module of modules) {
333
- appendModuleBuildJob(lines, module, branchRule);
334
- appendModuleTestJob(lines, module, branchRule);
335
- appendModuleReleaseJob(lines, module, branchRule);
376
+ appendModuleBuildJob(lines, module);
377
+ appendModuleTestJob(lines, module);
378
+ appendModuleReleaseJob(lines, module);
336
379
  }
337
- appendGlobalReleaseJob(lines, modules, mainBranch);
380
+ appendGlobalReleaseJob(lines, modules, mainBranch, tagTemplate, branchTagTemplate);
338
381
  for (const module of modules) {
339
- appendModulePublishJob(lines, module, branchRule);
340
- appendModuleDeployJob(lines, module, branchRule);
382
+ appendModulePublishJob(lines, module);
383
+ appendModuleDeployJob(lines, module);
341
384
  }
342
385
  return `${lines.join("\n").trimEnd()}\n`;
343
386
  }
344
387
  function main() {
345
- const { cwd, output, modulesRoot, branchRule, mainBranch } = parseArgs();
388
+ const { cwd, output, modulesRoot, mainBranch, tagTemplate, branchTagTemplate, } = parseArgs();
346
389
  if (!output) {
347
390
  log.error("Output path is required. Use [output] or --output <path>.");
348
391
  process.exit(1);
@@ -352,7 +395,7 @@ function main() {
352
395
  log.error({ cwd, modulesRoot }, "No supported modules were discovered under modules root");
353
396
  process.exit(1);
354
397
  }
355
- const pipeline = buildPipeline(modules, branchRule, mainBranch);
398
+ const pipeline = buildPipeline(modules, mainBranch, tagTemplate, branchTagTemplate);
356
399
  const outputPath = path.resolve(cwd, output);
357
400
  fs.writeFileSync(outputPath, pipeline, "utf8");
358
401
  log.info({ modules: modules.map((m) => m.modulePath), outputPath }, "Generated GitLab pipeline");
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "polyci",
3
3
  "description": "Monorepo CI/CD utilities.",
4
- "version": "0.0.6",
4
+ "version": "0.0.8",
5
5
  "type": "module",
6
6
  "private": false,
7
7
  "author": "Alexander Tsarev",