polyci 0.0.21 → 0.0.23

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 +81 -85
  2. package/package.json +1 -1
package/dist/main.js CHANGED
@@ -5,15 +5,22 @@ import pino from "pino";
5
5
  import pretty from "pino-pretty";
6
6
  const log = pino(pretty());
7
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) {
8
+ const DEFAULT_VERSION_TEMPLATE = "{version}";
9
+ const DEFAULT_BRANCH_VERSION_TEMPLATE = "{version}-{branch}-{increment}";
10
+ const DEFAULT_TAG_PATTERN = "^$MODULE_NAME-v(?<version>\\d+(\\.\\d+)*)-$CI_COMMIT_BRANCH-(?<increment>\\d+(\\.\\d+)*)$";
11
+ const ALLOWED_TAG_PLACEHOLDERS = new Set(["module", "version"]);
12
+ const ALLOWED_VERSION_PLACEHOLDERS = new Set(["branch", "version", "increment"]);
13
+ function applyVersionTemplate(template) {
11
14
  return template
12
- .replaceAll("{module}", moduleName)
13
15
  .replaceAll("{branch}", "${CI_COMMIT_BRANCH}")
14
16
  .replaceAll("{version}", "${NEXT_VERSION}")
15
17
  .replaceAll("{increment}", "${NEXT_INCREMENT}");
16
18
  }
19
+ function applyTagTemplate(template, moduleName, version) {
20
+ return template
21
+ .replaceAll("{module}", moduleName)
22
+ .replaceAll("{version}", version);
23
+ }
17
24
  function getInvalidTagTemplatePlaceholders(template) {
18
25
  const matches = template.matchAll(/\{([^}]+)\}/g);
19
26
  const invalid = new Set();
@@ -25,6 +32,17 @@ function getInvalidTagTemplatePlaceholders(template) {
25
32
  }
26
33
  return [...invalid];
27
34
  }
35
+ function getInvalidVersionTemplatePlaceholders(template) {
36
+ const matches = template.matchAll(/\{([^}]+)\}/g);
37
+ const invalid = new Set();
38
+ for (const match of matches) {
39
+ const placeholder = match[1];
40
+ if (!ALLOWED_VERSION_PLACEHOLDERS.has(placeholder)) {
41
+ invalid.add(placeholder);
42
+ }
43
+ }
44
+ return [...invalid];
45
+ }
28
46
  function parseArgs() {
29
47
  const program = new Command();
30
48
  program
@@ -32,8 +50,10 @@ function parseArgs() {
32
50
  .option("-o, --output <path>", "Output pipeline file path")
33
51
  .option("--modules-root <path>", "Modules root directory", "./modules")
34
52
  .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)
53
+ .option("--version-template <template>", "Template for primary branch module version; placeholders {version}, {branch}, {increment}", DEFAULT_VERSION_TEMPLATE)
54
+ .option("--branch-version-template <template>", "Template for non-primary branch module version; placeholders {version}, {branch}, {increment}", DEFAULT_BRANCH_VERSION_TEMPLATE)
55
+ .option("--tag-template <template>", "Template for non-primary branch tags; use placeholders {module}, {version}", DEFAULT_TAG_TEMPLATE)
56
+ .option("--tag-pattern <pattern>", "Tag pattern passed to semalease to find the latest tag and calculate the next version/increment", DEFAULT_TAG_PATTERN)
37
57
  .option("--cwd <path>", "Working directory", process.cwd())
38
58
  .parse();
39
59
  const options = program.opts();
@@ -41,17 +61,25 @@ function parseArgs() {
41
61
  const cwd = path.resolve(options.cwd);
42
62
  const modulesRoot = path.resolve(cwd, options.modulesRoot);
43
63
  const mainBranch = options.mainBranch;
64
+ const versionTemplate = options.versionTemplate;
65
+ const branchVersionTemplate = options.branchVersionTemplate;
44
66
  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
67
+ const tagPattern = options.tagPattern;
68
+ const invalidVersionTemplate = getInvalidVersionTemplatePlaceholders(versionTemplate);
69
+ if (invalidVersionTemplate.length > 0) {
70
+ program.error(`Invalid --version-template placeholders: ${invalidVersionTemplate
49
71
  .map((x) => `{${x}}`)
50
- .join(", ")}. Allowed: {module}, {branch}, {version}, {increment}.`);
72
+ .join(", ")}. Allowed: {version}, {branch}, {increment}.`);
51
73
  }
52
- const invalidSecondary = getInvalidTagTemplatePlaceholders(branchTagTemplate);
53
- if (invalidSecondary.length > 0) {
54
- program.error(`Invalid --branch-tag-template placeholders: ${invalidSecondary
74
+ const invalidBranchVersionTemplate = getInvalidVersionTemplatePlaceholders(branchVersionTemplate);
75
+ if (invalidBranchVersionTemplate.length > 0) {
76
+ program.error(`Invalid --branch-version-template placeholders: ${invalidBranchVersionTemplate
77
+ .map((x) => `{${x}}`)
78
+ .join(", ")}. Allowed: {version}, {branch}, {increment}.`);
79
+ }
80
+ const invalidTagTemplate = getInvalidTagTemplatePlaceholders(tagTemplate);
81
+ if (invalidTagTemplate.length > 0) {
82
+ program.error(`Invalid --tag-template placeholders: ${invalidTagTemplate
55
83
  .map((x) => `{${x}}`)
56
84
  .join(", ")}. Allowed: {module}, {branch}, {version}, {increment}.`);
57
85
  }
@@ -60,20 +88,15 @@ function parseArgs() {
60
88
  output,
61
89
  modulesRoot,
62
90
  mainBranch,
91
+ versionTemplate,
92
+ branchVersionTemplate,
63
93
  tagTemplate,
64
- branchTagTemplate,
94
+ tagPattern,
65
95
  };
66
96
  }
67
97
  function toPosixPath(p) {
68
98
  return p.split(path.sep).join("/");
69
99
  }
70
- function toJobId(value) {
71
- return value
72
- .replace(/[^a-zA-Z0-9_]/g, "_")
73
- .replace(/_+/g, "_")
74
- .replace(/^_+|_+$/g, "")
75
- .toLowerCase();
76
- }
77
100
  function detectModuleType(directory) {
78
101
  const baseName = path.basename(directory);
79
102
  if (baseName === "node_modules" || baseName === ".git" || baseName === "dist") {
@@ -112,7 +135,6 @@ function discoverModulesRecursively(repoRoot, modulesRoot, scanDir, modules) {
112
135
  moduleName,
113
136
  modulePath: relativeModulePath,
114
137
  moduleType,
115
- jobId: toJobId(relativeModulePath),
116
138
  });
117
139
  return;
118
140
  }
@@ -152,7 +174,7 @@ function appendGlobalVariables(lines) {
152
174
  lines.push(``);
153
175
  }
154
176
  function appendModuleBuildJob(lines, module) {
155
- lines.push(`${module.jobId}_build:`);
177
+ lines.push(`${module.moduleName}-build:`);
156
178
  lines.push(` stage: build`);
157
179
  lines.push(` rules:`);
158
180
  lines.push(` - changes:`);
@@ -173,13 +195,13 @@ function appendModuleBuildJob(lines, module) {
173
195
  lines.push(``);
174
196
  }
175
197
  function appendModuleTestJob(lines, module) {
176
- lines.push(`${module.jobId}_test:`);
198
+ lines.push(`${module.moduleName}-test:`);
177
199
  lines.push(` stage: test`);
178
200
  lines.push(` rules:`);
179
201
  lines.push(` - changes:`);
180
202
  lines.push(` - ${module.modulePath}/**/*`);
181
203
  lines.push(` needs:`);
182
- lines.push(` - job: ${module.jobId}_build`);
204
+ lines.push(` - job: ${module.moduleName}-build`);
183
205
  lines.push(` image: $IMAGE_LINUX`);
184
206
  lines.push(` variables:`);
185
207
  lines.push(` MODULE_NAME: ${module.moduleName}`);
@@ -190,15 +212,15 @@ function appendModuleTestJob(lines, module) {
190
212
  lines.push(` - echo "test is tasty"`);
191
213
  lines.push(``);
192
214
  }
193
- function appendModuleReleaseJob(lines, module) {
194
- lines.push(`${module.jobId}_release:`);
215
+ function appendModuleReleaseJob(lines, module, tagPattern) {
216
+ lines.push(`${module.moduleName}-release:`);
195
217
  lines.push(` stage: release`);
196
218
  lines.push(` rules:`);
197
219
  lines.push(` - changes:`);
198
220
  lines.push(` - ${module.modulePath}/**/*`);
199
221
  lines.push(` needs:`);
200
- lines.push(` - job: ${module.jobId}_build`);
201
- lines.push(` - job: ${module.jobId}_test`);
222
+ lines.push(` - job: ${module.moduleName}-build`);
223
+ lines.push(` - job: ${module.moduleName}-test`);
202
224
  lines.push(` image: $IMAGE_NODE`);
203
225
  lines.push(` variables:`);
204
226
  lines.push(` MODULE_NAME: ${module.moduleName}`);
@@ -207,46 +229,21 @@ function appendModuleReleaseJob(lines, module) {
207
229
  lines.push(` script:`);
208
230
  lines.push(` - apk update`);
209
231
  lines.push(` - apk add $GIT_PACKAGE`);
210
- lines.push(` - cp -r ci $MODULE_PATH/ci`);
211
- lines.push(` - cd $MODULE_PATH`);
212
- lines.push(` - npm ci`);
213
- lines.push(` - git tag -l "^${module.moduleName}-v(?<version>\\d+(\\.\\d+)*)-$CI_COMMIT_BRANCH-(?<increment>\\d+(\\.\\d+)*)$"`);
214
- lines.push(` - npx semalease --tag-pattern "^${module.moduleName}-v(?<version>\\d+(\\.\\d+)*)-$CI_COMMIT_BRANCH-(?<increment>\\d+(\\.\\d+)*)$" semalease.env`);
215
- lines.push(` - source semalease.env`);
216
- lines.push(` - |`);
217
- lines.push(` if [ "$CI_COMMIT_BRANCH" = "main" ]; then`);
218
- lines.push(` PACKAGE_VERSION="$NEXT_VERSION"`);
219
- lines.push(` else`);
220
- lines.push(` PACKAGE_VERSION="$NEXT_VERSION-$CI_COMMIT_BRANCH-$NEXT_INCREMENT"`);
221
- lines.push(` fi`);
222
- lines.push(` - |`);
223
- lines.push(` if [ "$MODULE_TYPE" = "node-express" ]; then`);
224
- lines.push(` node -e "const fs=require('fs'); const pkg=JSON.parse(fs.readFileSync('package.json','utf8')); pkg.version=process.argv[1]; fs.writeFileSync('package.json', JSON.stringify(pkg, null, 2) + '\\n');" "$PACKAGE_VERSION"`);
225
- lines.push(` node ci/prepare-deploy-variables.js --instance-branch $CI_COMMIT_BRANCH`);
226
- lines.push(` elif [ "$MODULE_TYPE" = "node-vite" ]; then`);
227
- lines.push(` node ci/set-release-data.js -i $CI_COMMIT_BRANCH --version=$PACKAGE_VERSION -t`);
228
- lines.push(` node -e "const fs=require('fs'); const pkg=JSON.parse(fs.readFileSync('package.json','utf8')); pkg.version=process.argv[1]; fs.writeFileSync('package.json', JSON.stringify(pkg, null, 2) + '\\n');" "$PACKAGE_VERSION"`);
229
- lines.push(` node ci/prepare-deploy-variables.js --instance-branch $CI_COMMIT_BRANCH`);
230
- lines.push(` else`);
231
- lines.push(` echo "Invalid module type: $MODULE_TYPE"`);
232
- lines.push(` exit 1`);
233
- lines.push(` fi`);
232
+ lines.push(` - cd ${module.modulePath}`);
233
+ lines.push(` - npx semalease --tag-pattern "${tagPattern}" semalease.env`);
234
234
  lines.push(` artifacts:`);
235
235
  lines.push(` paths:`);
236
236
  lines.push(` - $MODULE_PATH/dist`);
237
- lines.push(` - $MODULE_PATH/package.json`);
238
- lines.push(` - $MODULE_PATH/public/release.json`);
239
237
  lines.push(` - $MODULE_PATH/semalease.env`);
240
- lines.push(` - $MODULE_PATH/deploy.env`);
241
238
  lines.push(` expire_in: 1 day`);
242
239
  lines.push(``);
243
240
  }
244
- function appendGlobalReleaseJob(lines, modules, mainBranch, tagTemplate, branchTagTemplate) {
245
- lines.push(`release_and_tag:`);
241
+ function appendGlobalReleaseJob(lines, modules, mainBranch, versionTemplate, branchVersionTemplate, tagTemplate) {
242
+ lines.push(`release:`);
246
243
  lines.push(` stage: release`);
247
244
  lines.push(` needs:`);
248
245
  for (const module of modules) {
249
- lines.push(` - job: ${module.jobId}_release`);
246
+ lines.push(` - job: ${module.moduleName}-release`);
250
247
  lines.push(` optional: true`);
251
248
  }
252
249
  lines.push(` image: $IMAGE_NODE`);
@@ -262,35 +259,34 @@ function appendGlobalReleaseJob(lines, modules, mainBranch, tagTemplate, branchT
262
259
  lines.push(` - TAG_NAMES=""`);
263
260
  lines.push(``);
264
261
  for (const module of modules) {
265
- lines.push(` - echo "Tagging release for ${module.moduleName} (${module.modulePath})"`);
266
262
  lines.push(` - |`);
267
263
  lines.push(` if [ -f "${module.modulePath}/semalease.env" ]; then`);
268
264
  lines.push(` . "${module.modulePath}/semalease.env"`);
269
- lines.push(` TAG_NAME=""`);
265
+ lines.push(` MODULE_TAG=""`);
270
266
  lines.push(` if [ "$CI_COMMIT_BRANCH" = "${mainBranch}" ]; then`);
271
267
  lines.push(` if [ "\${NEXT_VERSION:-}" != "\${LATEST_VERSION:-}" ]; then`);
272
- lines.push(` TAG_NAME="${applyTagTemplate(tagTemplate, module.moduleName)}"`);
268
+ lines.push(` MODULE_VERSION="${applyVersionTemplate(versionTemplate)}"`);
269
+ lines.push(` MODULE_TAG="${applyTagTemplate(tagTemplate, module.moduleName, applyVersionTemplate(versionTemplate))}"`);
273
270
  lines.push(` else`);
274
- lines.push(` echo "Version did not change for ${module.moduleName} on branch $CI_COMMIT_BRANCH, skipping tag"`);
271
+ lines.push(` echo "Version did not change for ${module.moduleName} on branch $CI_COMMIT_BRANCH, skipping release."`);
275
272
  lines.push(` fi`);
276
273
  lines.push(` else`);
277
274
  lines.push(` if [ "\${NEXT_VERSION:-}" != "\${LATEST_VERSION:-}" ] || [ "\${NEXT_INCREMENT:-}" != "\${LATEST_INCREMENT:-}" ]; then`);
278
- lines.push(` TAG_NAME="${applyTagTemplate(branchTagTemplate, module.moduleName)}"`);
275
+ lines.push(` MODULE_VERSION="${applyVersionTemplate(branchVersionTemplate)}"`);
276
+ lines.push(` MODULE_TAG="${applyTagTemplate(tagTemplate, module.moduleName, applyVersionTemplate(branchVersionTemplate))}"`);
279
277
  lines.push(` else`);
280
- lines.push(` echo "Version/increment did not change for ${module.moduleName} on branch $CI_COMMIT_BRANCH, skipping tag"`);
278
+ lines.push(` echo "Version/increment did not change for ${module.moduleName} on branch $CI_COMMIT_BRANCH, skipping release."`);
281
279
  lines.push(` fi`);
282
280
  lines.push(` fi`);
283
- lines.push(` if [ "$TAG_NAME" != "" ]; then`);
284
- if (module.moduleType === "node-express") {
285
- lines.push(` git add "${module.modulePath}/package.json"`);
286
- }
287
- else if (module.moduleType === "node-vite") {
288
- lines.push(` git add "${module.modulePath}/package.json" "${module.modulePath}/public/release.json"`);
289
- }
290
- lines.push(` TAG_NAMES="\${TAG_NAMES} \${TAG_NAME}"`);
281
+ lines.push(` if [ "$MODULE_TAG" != "" ]; then`);
282
+ lines.push(` cp -r ci "${module.modulePath}/ci"`);
283
+ lines.push(` cd "${module.modulePath}"`);
284
+ lines.push(` source "ci/${module.moduleType}/release.sh"`);
285
+ lines.push(` cd -`);
286
+ lines.push(` TAG_NAMES="\${TAG_NAMES} \${MODULE_TAG}"`);
291
287
  lines.push(` fi`);
292
288
  lines.push(` else`);
293
- lines.push(` echo "Missing ${module.modulePath}/semalease.env, skipping ${module.moduleName}"`);
289
+ lines.push(` echo "Missing ${module.modulePath}/semalease.env, skipping ${module.moduleName} release."`);
294
290
  lines.push(` fi`);
295
291
  }
296
292
  lines.push(``);
@@ -315,14 +311,14 @@ function appendGlobalReleaseJob(lines, modules, mainBranch, tagTemplate, branchT
315
311
  lines.push(``);
316
312
  }
317
313
  function appendModulePublishJob(lines, module) {
318
- lines.push(`${module.jobId}_publish:`);
314
+ lines.push(`${module.moduleName}-publish:`);
319
315
  lines.push(` stage: publish`);
320
316
  lines.push(` rules:`);
321
317
  lines.push(` - changes:`);
322
318
  lines.push(` - ${module.modulePath}/**/*`);
323
319
  lines.push(` needs:`);
324
- lines.push(` - job: ${module.jobId}_test`);
325
- lines.push(` - job: release_and_tag`);
320
+ lines.push(` - job: ${module.moduleName}-test`);
321
+ lines.push(` - job: release`);
326
322
  lines.push(` image: $IMAGE_DOCKER`);
327
323
  lines.push(` variables:`);
328
324
  lines.push(` MODULE_NAME: ${module.moduleName}`);
@@ -342,13 +338,13 @@ function appendModulePublishJob(lines, module) {
342
338
  lines.push(``);
343
339
  }
344
340
  function appendModuleDeployJob(lines, module) {
345
- lines.push(`${module.jobId}_deploy:`);
341
+ lines.push(`${module.moduleName}-deploy:`);
346
342
  lines.push(` stage: deploy`);
347
343
  lines.push(` rules:`);
348
344
  lines.push(` - changes:`);
349
345
  lines.push(` - ${module.modulePath}/**/*`);
350
346
  lines.push(` needs:`);
351
- lines.push(` - job: ${module.jobId}_publish`);
347
+ lines.push(` - job: ${module.moduleName}-publish`);
352
348
  lines.push(` image: $IMAGE_COMPOSE`);
353
349
  lines.push(` variables:`);
354
350
  lines.push(` MODULE_NAME: ${module.moduleName}`);
@@ -376,15 +372,15 @@ function appendModuleDeployJob(lines, module) {
376
372
  lines.push(` - rm -rf ~/.ssh`);
377
373
  lines.push(``);
378
374
  }
379
- function buildPipeline(modules, mainBranch, tagTemplate, branchTagTemplate) {
375
+ function buildPipeline(modules, mainBranch, versionTemplate, branchVersionTemplate, tagTemplate, tagPattern) {
380
376
  const lines = [];
381
377
  appendGlobalVariables(lines);
382
378
  for (const module of modules) {
383
379
  appendModuleBuildJob(lines, module);
384
380
  appendModuleTestJob(lines, module);
385
- appendModuleReleaseJob(lines, module);
381
+ appendModuleReleaseJob(lines, module, tagPattern);
386
382
  }
387
- appendGlobalReleaseJob(lines, modules, mainBranch, tagTemplate, branchTagTemplate);
383
+ appendGlobalReleaseJob(lines, modules, mainBranch, versionTemplate, branchVersionTemplate, tagTemplate);
388
384
  for (const module of modules) {
389
385
  appendModulePublishJob(lines, module);
390
386
  appendModuleDeployJob(lines, module);
@@ -392,7 +388,7 @@ function buildPipeline(modules, mainBranch, tagTemplate, branchTagTemplate) {
392
388
  return `${lines.join("\n").trimEnd()}\n`;
393
389
  }
394
390
  function main() {
395
- const { cwd, output, modulesRoot, mainBranch, tagTemplate, branchTagTemplate, } = parseArgs();
391
+ const { cwd, output, modulesRoot, mainBranch, versionTemplate, branchVersionTemplate, tagTemplate, tagPattern } = parseArgs();
396
392
  if (!output) {
397
393
  log.error("Output path is required. Use [output] or --output <path>.");
398
394
  process.exit(1);
@@ -402,7 +398,7 @@ function main() {
402
398
  log.error({ cwd, modulesRoot }, "No supported modules were discovered under modules root");
403
399
  process.exit(1);
404
400
  }
405
- const pipeline = buildPipeline(modules, mainBranch, tagTemplate, branchTagTemplate);
401
+ const pipeline = buildPipeline(modules, mainBranch, versionTemplate, branchVersionTemplate, tagTemplate, tagPattern);
406
402
  const outputPath = path.resolve(cwd, output);
407
403
  fs.writeFileSync(outputPath, pipeline, "utf8");
408
404
  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.21",
4
+ "version": "0.0.23",
5
5
  "type": "module",
6
6
  "private": false,
7
7
  "author": "Alexander Tsarev",