polyci 0.0.5 → 0.0.7

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 +74 -25
  2. package/package.json +1 -1
package/dist/main.js CHANGED
@@ -4,22 +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)
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)
15
37
  .option("--cwd <path>", "Working directory", process.cwd())
16
38
  .parse();
17
39
  const options = program.opts();
18
40
  const output = options.output ?? program.args[0] ?? "";
19
41
  const cwd = path.resolve(options.cwd);
20
42
  const modulesRoot = path.resolve(cwd, options.modulesRoot);
21
- const branchRule = options.branchRule;
22
- return { cwd, output, modulesRoot, branchRule };
43
+ const mainBranch = options.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
+ };
23
66
  }
24
67
  function toPosixPath(p) {
25
68
  return p.split(path.sep).join("/");
@@ -107,7 +150,7 @@ function appendGlobalVariables(lines) {
107
150
  lines.push(" - deploy");
108
151
  lines.push("");
109
152
  }
110
- function appendModuleBuildJob(lines, module, branchRule) {
153
+ function appendModuleBuildJob(lines, module) {
111
154
  lines.push(`${module.jobId}_build:`);
112
155
  lines.push(" stage: build");
113
156
  lines.push(" rules:");
@@ -128,7 +171,7 @@ function appendModuleBuildJob(lines, module, branchRule) {
128
171
  lines.push(" expire_in: 1 day");
129
172
  lines.push("");
130
173
  }
131
- function appendModuleTestJob(lines, module, branchRule) {
174
+ function appendModuleTestJob(lines, module) {
132
175
  lines.push(`${module.jobId}_test:`);
133
176
  lines.push(" stage: test");
134
177
  lines.push(" rules:");
@@ -146,7 +189,7 @@ function appendModuleTestJob(lines, module, branchRule) {
146
189
  lines.push(' - echo "test is tasty"');
147
190
  lines.push("");
148
191
  }
149
- function appendModuleReleaseJob(lines, module, branchRule) {
192
+ function appendModuleReleaseJob(lines, module) {
150
193
  lines.push(`${module.jobId}_release:`);
151
194
  lines.push(" stage: release");
152
195
  lines.push(" rules:");
@@ -195,7 +238,7 @@ function appendModuleReleaseJob(lines, module, branchRule) {
195
238
  lines.push(` expire_in: 1 day`);
196
239
  lines.push("");
197
240
  }
198
- function appendGlobalReleaseJob(lines, modules) {
241
+ function appendGlobalReleaseJob(lines, modules, mainBranch, tagTemplate, branchTagTemplate) {
199
242
  lines.push("release_and_tag:");
200
243
  lines.push(" stage: release");
201
244
  lines.push(" needs:");
@@ -214,21 +257,22 @@ function appendGlobalReleaseJob(lines, modules) {
214
257
  lines.push(" git config user.email \"polyci@anarun.net\"");
215
258
  lines.push(" git config user.name \"polyci\"");
216
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");
217
261
  lines.push("");
218
262
  for (const module of modules) {
219
263
  lines.push(` echo "Tagging release for ${module.moduleName} (${module.modulePath})"`);
220
264
  lines.push(` if [ -f "${module.modulePath}/semalease.env" ]; then`);
221
265
  lines.push(` . "${module.modulePath}/semalease.env"`);
222
266
  lines.push(" TAG_NAME=\"\"");
223
- lines.push(" if [ \"$CI_COMMIT_BRANCH\" = \"main\" ]; then");
267
+ lines.push(` if [ "$CI_COMMIT_BRANCH" = "${mainBranch}" ]; then`);
224
268
  lines.push(" if [ \"${NEXT_VERSION:-}\" != \"${LATEST_VERSION:-}\" ]; then");
225
- lines.push(` TAG_NAME="${module.moduleName}-v\${NEXT_VERSION}"`);
269
+ lines.push(` TAG_NAME="${applyTagTemplate(tagTemplate, module.moduleName)}"`);
226
270
  lines.push(" else");
227
271
  lines.push(` echo "Version did not change for ${module.moduleName} on branch $CI_COMMIT_BRANCH, skipping tag"`);
228
272
  lines.push(" fi");
229
273
  lines.push(" else");
230
274
  lines.push(" if [ \"${NEXT_VERSION:-}\" != \"${LATEST_VERSION:-}\" ] || [ \"${NEXT_INCREMENT:-}\" != \"${LATEST_INCREMENT:-}\" ]; then");
231
- lines.push(` TAG_NAME="${module.moduleName}-v\${NEXT_VERSION}-\${CI_COMMIT_BRANCH}-\${NEXT_INCREMENT}"`);
275
+ lines.push(` TAG_NAME="${applyTagTemplate(branchTagTemplate, module.moduleName)}"`);
232
276
  lines.push(" else");
233
277
  lines.push(` echo "Version/increment did not change for ${module.moduleName} on branch $CI_COMMIT_BRANCH, skipping tag"`);
234
278
  lines.push(" fi");
@@ -241,15 +285,20 @@ function appendGlobalReleaseJob(lines, modules) {
241
285
  lines.push(` git add "${module.modulePath}/package.json" "${module.modulePath}/public/release.json"`);
242
286
  }
243
287
  lines.push(" git tag \"$TAG_NAME\"");
288
+ lines.push(" TAG_CREATED=true");
244
289
  lines.push(" fi");
245
290
  lines.push(" else");
246
291
  lines.push(` echo "Missing ${module.modulePath}/semalease.env, skipping ${module.moduleName}"`);
247
292
  lines.push(" fi");
248
293
  }
249
294
  lines.push("");
250
- lines.push(" git commit -m \"release: update affected modules\"");
251
- lines.push(" git push origin HEAD");
252
- lines.push(" git push origin --tags");
295
+ lines.push(" if [ \"$TAG_CREATED\" = true ]; then");
296
+ lines.push(" git commit -m \"release: update affected modules\"");
297
+ lines.push(" git push origin HEAD");
298
+ lines.push(" git push origin --tags");
299
+ lines.push(" else");
300
+ lines.push(" echo \"No new tags created; skipping commit/push\"");
301
+ lines.push(" fi");
253
302
  lines.push(` artifacts:`);
254
303
  lines.push(` paths:`);
255
304
  for (const module of modules) {
@@ -259,7 +308,7 @@ function appendGlobalReleaseJob(lines, modules) {
259
308
  lines.push(` expire_in: 1 day`);
260
309
  lines.push("");
261
310
  }
262
- function appendModulePublishJob(lines, module, branchRule) {
311
+ function appendModulePublishJob(lines, module) {
263
312
  lines.push(`${module.jobId}_publish:`);
264
313
  lines.push(" stage: publish");
265
314
  lines.push(" rules:");
@@ -287,7 +336,7 @@ function appendModulePublishJob(lines, module, branchRule) {
287
336
  lines.push(" expire_in: 1 day");
288
337
  lines.push("");
289
338
  }
290
- function appendModuleDeployJob(lines, module, branchRule) {
339
+ function appendModuleDeployJob(lines, module) {
291
340
  lines.push(`${module.jobId}_deploy:`);
292
341
  lines.push(" stage: deploy");
293
342
  lines.push(" rules:");
@@ -318,23 +367,23 @@ function appendModuleDeployJob(lines, module, branchRule) {
318
367
  lines.push(" - rm -rf ~/.ssh");
319
368
  lines.push("");
320
369
  }
321
- function buildPipeline(modules, branchRule) {
370
+ function buildPipeline(modules, mainBranch, tagTemplate, branchTagTemplate) {
322
371
  const lines = [];
323
372
  appendGlobalVariables(lines);
324
373
  for (const module of modules) {
325
- appendModuleBuildJob(lines, module, branchRule);
326
- appendModuleTestJob(lines, module, branchRule);
327
- appendModuleReleaseJob(lines, module, branchRule);
374
+ appendModuleBuildJob(lines, module);
375
+ appendModuleTestJob(lines, module);
376
+ appendModuleReleaseJob(lines, module);
328
377
  }
329
- appendGlobalReleaseJob(lines, modules);
378
+ appendGlobalReleaseJob(lines, modules, mainBranch, tagTemplate, branchTagTemplate);
330
379
  for (const module of modules) {
331
- appendModulePublishJob(lines, module, branchRule);
332
- appendModuleDeployJob(lines, module, branchRule);
380
+ appendModulePublishJob(lines, module);
381
+ appendModuleDeployJob(lines, module);
333
382
  }
334
383
  return `${lines.join("\n").trimEnd()}\n`;
335
384
  }
336
385
  function main() {
337
- const { cwd, output, modulesRoot, branchRule } = parseArgs();
386
+ const { cwd, output, modulesRoot, mainBranch, tagTemplate, branchTagTemplate, } = parseArgs();
338
387
  if (!output) {
339
388
  log.error("Output path is required. Use [output] or --output <path>.");
340
389
  process.exit(1);
@@ -344,7 +393,7 @@ function main() {
344
393
  log.error({ cwd, modulesRoot }, "No supported modules were discovered under modules root");
345
394
  process.exit(1);
346
395
  }
347
- const pipeline = buildPipeline(modules, branchRule);
396
+ const pipeline = buildPipeline(modules, mainBranch, tagTemplate, branchTagTemplate);
348
397
  const outputPath = path.resolve(cwd, output);
349
398
  fs.writeFileSync(outputPath, pipeline, "utf8");
350
399
  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.5",
4
+ "version": "0.0.7",
5
5
  "type": "module",
6
6
  "private": false,
7
7
  "author": "Alexander Tsarev",