@firestartr/cli 1.59.3-snapshot-15 → 1.60.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.
package/build/index.js CHANGED
@@ -346942,6 +346942,69 @@ function policiesAreCompatible(syncPolicy, generalPolicy) {
346942
346942
  FIRESTARTR_POLICIES: FIRESTARTR_POLICIES,
346943
346943
  });
346944
346944
 
346945
+ ;// CONCATENATED MODULE: ../catalog_common/src/tokenizer/index.ts
346946
+ class SimpleTokenizer {
346947
+ /**
346948
+ * Pass the regex here (it automatically makes it global so it finds ALL matches)
346949
+ */
346950
+ constructor(regex) {
346951
+ const flags = regex.flags.includes('g') ? regex.flags : regex.flags + 'g';
346952
+ this.regex = new RegExp(regex.source, flags);
346953
+ }
346954
+ /**
346955
+ * Turn the whole string into a list of tokens:
346956
+ * - 'text' = normal text between matches
346957
+ * - 'match' = whatever your regex found
346958
+ */
346959
+ tokenize(input) {
346960
+ this.regex.lastIndex = 0;
346961
+ const tokens = [];
346962
+ let lastIndex = 0;
346963
+ let match;
346964
+ while ((match = this.regex.exec(input)) !== null) {
346965
+ // Text before this match
346966
+ if (match.index > lastIndex) {
346967
+ tokens.push({
346968
+ type: 'text',
346969
+ value: input.slice(lastIndex, match.index),
346970
+ index: lastIndex,
346971
+ });
346972
+ }
346973
+ // The matched part
346974
+ tokens.push({
346975
+ type: 'match',
346976
+ value: match[0],
346977
+ index: match.index,
346978
+ groups: match.slice(1), // any capture groups you defined
346979
+ });
346980
+ if (match[0].length === 0) {
346981
+ this.regex.lastIndex += 1;
346982
+ }
346983
+ lastIndex = this.regex.lastIndex;
346984
+ }
346985
+ // Remaining text after the last match
346986
+ if (lastIndex < input.length) {
346987
+ tokens.push({
346988
+ type: 'text',
346989
+ value: input.slice(lastIndex),
346990
+ index: lastIndex,
346991
+ });
346992
+ }
346993
+ return tokens;
346994
+ }
346995
+ /**
346996
+ * Replace only the matched tokens by calling the function.
346997
+ */
346998
+ replace(input, replacer) {
346999
+ return this.tokenize(input)
347000
+ .map((token) => (token.type === 'match' ? replacer(token) : token.value))
347001
+ .join('');
347002
+ }
347003
+ }
347004
+ /* harmony default export */ const tokenizer = ({
347005
+ SimpleTokenizer,
347006
+ });
347007
+
346945
347008
  // EXTERNAL MODULE: ../../node_modules/cron-parser/dist/index.js
346946
347009
  var cron_parser_dist = __nccwpck_require__(98370);
346947
347010
  ;// CONCATENATED MODULE: ../catalog_common/src/cron.ts
@@ -346984,6 +347047,7 @@ function getCronNextInterval(cronLine, tz) {
346984
347047
 
346985
347048
 
346986
347049
 
347050
+
346987
347051
  /* harmony default export */ const catalog_common = ({
346988
347052
  io: io,
346989
347053
  generic: generic,
@@ -346993,6 +347057,7 @@ function getCronNextInterval(cronLine, tz) {
346993
347057
  features: features,
346994
347058
  policies: policies,
346995
347059
  logger: logger_logger,
347060
+ tokenizer: tokenizer,
346996
347061
  cron: {
346997
347062
  validateCron: validateCron,
346998
347063
  isValidCron: isValidCron,
@@ -353830,6 +353895,7 @@ async function getOrgTeamsDirectAccess(org) {
353830
353895
  nodes {
353831
353896
  id
353832
353897
  name
353898
+ slug
353833
353899
  repositories {
353834
353900
  edges {
353835
353901
  permission
@@ -353859,7 +353925,7 @@ function transformGraphQLResponse(response) {
353859
353925
  teams.forEach((team) => {
353860
353926
  const teamName = team.name;
353861
353927
  const teamId = team.id;
353862
- result.teams[teamName] = { id: teamId };
353928
+ result.teams[teamName] = { id: teamId, slug: team.slug };
353863
353929
  const repositories = team.repositories.edges;
353864
353930
  repositories.forEach((repoEdge) => {
353865
353931
  const repoName = repoEdge.node.name;
@@ -353867,7 +353933,10 @@ function transformGraphQLResponse(response) {
353867
353933
  if (!result.repositories[repoName]) {
353868
353934
  result.repositories[repoName] = {};
353869
353935
  }
353870
- result.repositories[repoName][teamName] = permission;
353936
+ result.repositories[repoName][teamName] = {
353937
+ permission,
353938
+ slug: team.slug,
353939
+ };
353871
353940
  });
353872
353941
  });
353873
353942
  return result;
@@ -355229,6 +355298,7 @@ class BranchStrategiesInitializer extends InitializerPatches {
355229
355298
  cr.spec.repo.defaultBranch = claim.providers.github.defaultBranch;
355230
355299
  cr.spec.branchProtections = [];
355231
355300
  }
355301
+ cr.spec.branchProtections = cr.spec?.branchProtections ?? [];
355232
355302
  delete cr.metadata.annotations[catalog_common.generic.getFirestartrAnnotation('branchStrategy')];
355233
355303
  return cr;
355234
355304
  },
@@ -356599,6 +356669,8 @@ class BranchStrategiesExpander extends GlobalSection {
356599
356669
  claim.providers.github.branchStrategy.defaultBranch;
356600
356670
  cr.spec.branchProtections = previousCR.spec.branchProtections;
356601
356671
  }
356672
+ // fallback to empty array
356673
+ cr.spec.branchProtections = cr.spec?.branchProtections ?? [];
356602
356674
  delete cr.metadata.annotations[catalog_common.generic.getFirestartrAnnotation('branchStrategy')];
356603
356675
  return cr;
356604
356676
  },
@@ -366393,15 +366465,12 @@ function walkArray(arr, symbolsToResolve, renderClaim) {
366393
366465
  */
366394
366466
  async function renderFromImports(rClaims, crs = {}, catalogOutputDir = '/tmp/.catalog', crOutputDir = '/tmp/.resources') {
366395
366467
  // Apply claim defaults (same as runRenderer path does via loadClaim/patchClaim)
366468
+ let defaults;
366396
366469
  try {
366397
- const defaults = loadClaimDefaults();
366398
- for (const renderClaim of Object.values(rClaims)) {
366399
- renderClaim.claim = loader_patchClaim(renderClaim.claim, defaults);
366400
- validateClaim(renderClaim.claim);
366401
- }
366470
+ defaults = loadClaimDefaults();
366402
366471
  }
366403
366472
  catch (e) {
366404
- cdk8s_renderer_src_logger.warn(`Could not apply claim defaults: ${e.message}`);
366473
+ cdk8s_renderer_src_logger.warn(`Could not load claim defaults: ${e.message}`);
366405
366474
  }
366406
366475
  // type: is the kind of the claim
366407
366476
  // value: is the imported-ref (the name in Github, can be with special characters, spaces, etc)
@@ -366419,6 +366488,13 @@ async function renderFromImports(rClaims, crs = {}, catalogOutputDir = '/tmp/.ca
366419
366488
  cdk8s_renderer_src_logger.error(`Error while resolving imported-refs: ${e.message}`);
366420
366489
  throw new Error(`Error while resolving imported-refs: ${e.message}`);
366421
366490
  }
366491
+ // after resolving the imported-refs, we need to validate the claims again, and apply the defaults if needed
366492
+ for (const renderClaim of Object.values(rClaims)) {
366493
+ if (defaults) {
366494
+ renderClaim.claim = loader_patchClaim(renderClaim.claim, defaults);
366495
+ }
366496
+ validateClaim(renderClaim.claim, `firestartr.dev://common/${renderClaim.claim.kind}`);
366497
+ }
366422
366498
  const catalogScope = new lib.App({
366423
366499
  outdir: catalogOutputDir,
366424
366500
  outputFileExtension: '.yaml',
@@ -367044,10 +367120,61 @@ MemberCollectionGithubDecanter.collectionKind = 'gh-members';
367044
367120
  applyCollectionMixins(MemberCollectionGithubDecanter);
367045
367121
  /* harmony default export */ const github_member_collection = (MemberCollectionGithubDecanter);
367046
367122
 
367123
+ ;// CONCATENATED MODULE: ../importer/src/utils/codeowner.ts
367124
+ function extractFromCodeOwners(codeOwnersContent) {
367125
+ if (!codeOwnersContent) {
367126
+ return [];
367127
+ }
367128
+ return codeOwnersContent.split(/\r?\n/).flatMap((line) => {
367129
+ const trimmedLine = line.trim();
367130
+ // skip blank lines and full-line comments
367131
+ if (!trimmedLine || trimmedLine.startsWith('#')) {
367132
+ return [];
367133
+ }
367134
+ // strip inline comments before parsing owners
367135
+ const lineWithoutInlineComment = line.replace(/\s*#.*$/, '').trim();
367136
+ if (!lineWithoutInlineComment) {
367137
+ return [];
367138
+ }
367139
+ // CODEOWNERS format: <pattern> <owner1> [<owner2> ...]
367140
+ // first field is the path pattern; owners start from the second field
367141
+ const parts = lineWithoutInlineComment.split(/\s+/);
367142
+ if (parts.length < 2) {
367143
+ return [];
367144
+ }
367145
+ return parts
367146
+ .slice(1)
367147
+ .filter((part) => part.startsWith('@'))
367148
+ .map((owner) => ({
367149
+ full: owner,
367150
+ isTeam: owner.includes('/'),
367151
+ name: owner,
367152
+ }));
367153
+ });
367154
+ }
367155
+
367156
+ ;// CONCATENATED MODULE: ../importer/src/utils/nomicon.ts
367157
+ function transformRepoName(repoName) {
367158
+ // Convert to lowercase
367159
+ let transformedName = repoName.toLowerCase();
367160
+ const specialCharsRegex = /[._]/g;
367161
+ if (specialCharsRegex.test(transformedName)) {
367162
+ transformedName = transformedName.replace(/[._]/g, '');
367163
+ // we add a random 2 letter suffix
367164
+ return (transformedName + '-imp-' + Math.random().toString(36).substring(2, 4));
367165
+ }
367166
+ else {
367167
+ return transformedName;
367168
+ }
367169
+ }
367170
+
367047
367171
  ;// CONCATENATED MODULE: ../importer/src/decanter/gh/github_repo.ts
367048
367172
 
367049
367173
 
367050
367174
 
367175
+
367176
+
367177
+
367051
367178
  const TYPE_MAP = {
367052
367179
  User: 'user',
367053
367180
  Team: 'group',
@@ -367065,7 +367192,7 @@ class RepoGithubDecanter extends GithubDecanter {
367065
367192
  version: this.VERSION(),
367066
367193
  type: 'service',
367067
367194
  lifecycle: 'production',
367068
- name: this.data.repoDetails.name,
367195
+ name: transformRepoName(this.data.repoDetails.name),
367069
367196
  };
367070
367197
  }
367071
367198
  __decantProviders() {
@@ -367118,6 +367245,52 @@ class RepoGithubDecanter extends GithubDecanter {
367118
367245
  });
367119
367246
  }
367120
367247
  }
367248
+ // only for validation
367249
+ __decantValidateCodeowners() {
367250
+ if (this.data.codeowners) {
367251
+ const owners = extractFromCodeOwners(this.data.codeowners);
367252
+ const normalizeTeamIdentifier = (value) => {
367253
+ const normalizedValue = (value.startsWith('@') ? value.slice(1) : value).toLowerCase();
367254
+ if (!normalizedValue.includes('/')) {
367255
+ return normalizedValue;
367256
+ }
367257
+ const [org, slug, ...rest] = normalizedValue.split('/');
367258
+ if (!org || !slug || rest.length > 0) {
367259
+ return null;
367260
+ }
367261
+ return org === this.org.toLowerCase() ? slug : null;
367262
+ };
367263
+ const isInTeams = (teamSlugInCodeowners) => {
367264
+ const normalizedTeamSlug = normalizeTeamIdentifier(teamSlugInCodeowners);
367265
+ if (!normalizedTeamSlug) {
367266
+ return false;
367267
+ }
367268
+ return this.data.teamsAndMembers.teams.some((team) => {
367269
+ return team.slug?.toLowerCase() === normalizedTeamSlug;
367270
+ });
367271
+ };
367272
+ const isInUsers = (userName) => {
367273
+ // strip the leading '@' for users (CODEOWNERS format: @username)
367274
+ // and normalize to lowercase for case-insensitive comparison
367275
+ const normalizedUserName = (userName.startsWith('@') ? userName.slice(1) : userName).toLowerCase();
367276
+ return (this.data.teamsAndMembers.directMembers.some((member) => member.name?.toLowerCase() === normalizedUserName) ||
367277
+ this.data.teamsAndMembers.outsideMembers.some((member) => member.name?.toLowerCase() === normalizedUserName));
367278
+ };
367279
+ // let's validate that every element in the
367280
+ // CODEOWNERS file is either a team or a user in the repository,
367281
+ // otherwise we will have dangling references in the claim which will cause issues down the line
367282
+ for (const owner of owners) {
367283
+ if (owner.isTeam && !isInTeams(owner.name)) {
367284
+ importer_src_logger.error(`[${this.org}/${this.data.repoDetails.name}] CODEOWNERS file references team ${owner.name} which is not present in the repository's teams.`);
367285
+ throw new Error(`[${this.org}/${this.data.repoDetails.name}] CODEOWNERS file references team ${owner.name} which is not present in the repository's teams.`);
367286
+ }
367287
+ else if (!owner.isTeam && !isInUsers(owner.name)) {
367288
+ importer_src_logger.error(`[${this.org}/${this.data.repoDetails.name}] CODEOWNERS file references user ${owner.name} which is not present in the repository's collaborators.`);
367289
+ throw new Error(`[${this.org}/${this.data.repoDetails.name}] CODEOWNERS file references user ${owner.name} which is not present in the repository's collaborators.`);
367290
+ }
367291
+ }
367292
+ }
367293
+ }
367121
367294
  __decantRelations() {
367122
367295
  const directMaintainers = this.data.teamsAndMembers.directMembers
367123
367296
  .filter((member) => member.role === 'maintain')
@@ -367232,7 +367405,8 @@ class RepoGithubDecanter extends GithubDecanter {
367232
367405
  const teams = Object.keys(this.githubTeams).map((teamName) => {
367233
367406
  return {
367234
367407
  name: teamName,
367235
- role: this.githubTeams[teamName].toLowerCase(),
367408
+ role: this.githubTeams[teamName].permission.toLowerCase(),
367409
+ slug: this.githubTeams[teamName].slug,
367236
367410
  };
367237
367411
  });
367238
367412
  this.data['teamsAndMembers']['teams'] = teams;
@@ -367260,6 +367434,28 @@ class RepoGithubDecanter extends GithubDecanter {
367260
367434
  }
367261
367435
  this.data['branchStrategy'] = { kind: value, data: bpInfo };
367262
367436
  }
367437
+ async __gatherCodeowners() {
367438
+ const codeownersPaths = [
367439
+ '.github/CODEOWNERS',
367440
+ 'CODEOWNERS',
367441
+ 'docs/CODEOWNERS',
367442
+ ];
367443
+ for (const codeownersPath of codeownersPaths) {
367444
+ try {
367445
+ const codeowners = await github_0.repo.getContent(codeownersPath, this.data.repoDetails.name, this.org);
367446
+ this.data['codeowners'] = codeowners;
367447
+ return;
367448
+ }
367449
+ catch (e) {
367450
+ const status = e?.status ?? e?.response?.status;
367451
+ if (status === 404) {
367452
+ continue;
367453
+ }
367454
+ throw e;
367455
+ }
367456
+ }
367457
+ importer_src_logger.info(`No CODEOWNERS file found for ${this.data.repoDetails.name}, skipping.`);
367458
+ }
367263
367459
  async __adaptInitializerBranchStrategies(_claim) {
367264
367460
  const bpData = this.data.branchStrategy.data;
367265
367461
  if (this.data.branchStrategy.kind === 'custom') {
@@ -367326,7 +367522,8 @@ class RepoCollectionGithubDecanter extends GithubDecanter {
367326
367522
  const filteredRepos = await this.filter(RepoCollectionGithubDecanter.collectionKind, filters, repoList.map((repo) => repo.name));
367327
367523
  for (const repoName of filteredRepos) {
367328
367524
  const repoInfo = await this.github.repo.getRepoInfo(this.org, repoName);
367329
- repos.push(new RepoGithubDecanter({ repoDetails: repoInfo }, this.org, directAccessRepos?.[repoName]));
367525
+ const repoDecanter = new RepoGithubDecanter({ repoDetails: repoInfo }, this.org, directAccessRepos?.[repoName]);
367526
+ repos.push(repoDecanter);
367330
367527
  }
367331
367528
  this.data['collection'] = repos;
367332
367529
  return repos;
@@ -367345,6 +367542,13 @@ applyCollectionMixins(RepoCollectionGithubDecanter);
367345
367542
 
367346
367543
 
367347
367544
 
367545
+ // A single claim can render multiple CRs.
367546
+ // Some of those rendered kinds are derived/child resources whose parent is the
367547
+ // main CR for the claim, so they do not correspond to claim files that should be moved.
367548
+ const CHILD_CR_KINDS = [
367549
+ 'FirestartrGithubRepositorySecretsSection',
367550
+ 'FirestartrGithubRepositoryFeature',
367551
+ ];
367348
367552
  let previousCRs = {};
367349
367553
  const collections = {
367350
367554
  GroupCollectionGithubDecanter: github_group_collection,
@@ -367415,7 +367619,10 @@ async function moveCRsAndClaims(crs, org, claimsPath, resourcesPath) {
367415
367619
  importer_src_logger.info(`📝 Moving: CR with kind: ${crs[k].kind} and name: ${crs[k].metadata.name} to wet repository`);
367416
367620
  importedResources.push(`${crs[k].kind} ${crs[k].metadata.name}`);
367417
367621
  catalog_common.io.moveFile(getCrPath(tmpRenderedCrsPath, crs[k]), getCrPath(resourcesPath, crs[k]));
367418
- catalog_common.io.moveFile(getClaimPathFromCR(randomFolder, crs[k]), getClaimPathFromCR(claimsPath, crs[k]));
367622
+ // We only want to execute the move on claims based on parent CRs, so we're omitting child CRs
367623
+ if (!CHILD_CR_KINDS.includes(crs[k].kind)) {
367624
+ catalog_common.io.moveFile(getClaimPathFromCR(randomFolder, crs[k]), getClaimPathFromCR(claimsPath, crs[k]));
367625
+ }
367419
367626
  }
367420
367627
  catch (e) {
367421
367628
  console.error(e);
@@ -377090,9 +377297,9 @@ const crs_analyzerSubcommand = {
377090
377297
  };
377091
377298
 
377092
377299
  ;// CONCATENATED MODULE: ./package.json
377093
- const package_namespaceObject = JSON.parse('{"i8":"1.59.3-snapshot-15"}');
377300
+ const package_namespaceObject = {"i8":"1.60.0"};
377094
377301
  ;// CONCATENATED MODULE: ../../package.json
377095
- const package_namespaceObject_1 = {"i8":"1.59.3"};
377302
+ const package_namespaceObject_1 = {"i8":"1.60.0"};
377096
377303
  ;// CONCATENATED MODULE: ./src/subcommands/index.ts
377097
377304
 
377098
377305
 
@@ -99,6 +99,9 @@ declare const _default: {
99
99
  verbose: (...args: any) => void;
100
100
  silly: (...args: any) => void;
101
101
  };
102
+ tokenizer: {
103
+ SimpleTokenizer: typeof import("./src/tokenizer").SimpleTokenizer;
104
+ };
102
105
  cron: {
103
106
  validateCron: typeof validateCron;
104
107
  isValidCron: typeof isValidCron;
@@ -0,0 +1,27 @@
1
+ export interface Token {
2
+ type: 'text' | 'match';
3
+ value: string;
4
+ index: number;
5
+ groups?: string[];
6
+ }
7
+ export declare class SimpleTokenizer {
8
+ private regex;
9
+ /**
10
+ * Pass the regex here (it automatically makes it global so it finds ALL matches)
11
+ */
12
+ constructor(regex: RegExp);
13
+ /**
14
+ * Turn the whole string into a list of tokens:
15
+ * - 'text' = normal text between matches
16
+ * - 'match' = whatever your regex found
17
+ */
18
+ tokenize(input: string): Token[];
19
+ /**
20
+ * Replace only the matched tokens by calling the function.
21
+ */
22
+ replace(input: string, replacer: (token: Token) => string): string;
23
+ }
24
+ declare const _default: {
25
+ SimpleTokenizer: typeof SimpleTokenizer;
26
+ };
27
+ export default _default;
@@ -8,11 +8,13 @@ export default class RepoGithubDecanter extends GithubDecanter {
8
8
  __decantProviders(): void;
9
9
  __decantPages(): void;
10
10
  __decantOIDC(): void;
11
+ __decantValidateCodeowners(): void;
11
12
  __decantRelations(): void;
12
13
  __gatherRepoTeamsAndMembers(): Promise<void>;
13
14
  __gatherPages(): Promise<void>;
14
15
  __gatherOIDCSubjectClaim(): Promise<void>;
15
16
  __gatherBranchStrategy(): Promise<void>;
17
+ __gatherCodeowners(): Promise<void>;
16
18
  __adaptInitializerBranchStrategies(_claim: any): Promise<BranchStrategiesInitializer>;
17
19
  __adaptInitializerBase(_claim: any): Promise<InitializerDefault>;
18
20
  __adaptOverriderRepo(_claim: any): Promise<GithubRepositoryOverrider>;
@@ -0,0 +1,5 @@
1
+ export declare function extractFromCodeOwners(codeOwnersContent: string): {
2
+ full: string;
3
+ isTeam: boolean;
4
+ name: string;
5
+ }[];
@@ -0,0 +1 @@
1
+ export declare function transformRepoName(repoName: string): string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@firestartr/cli",
3
- "version": "1.59.3-snapshot-15",
3
+ "version": "1.60.0",
4
4
  "private": false,
5
5
  "description": "Commandline tool",
6
6
  "main": "build/main.js",