@opencrvs/create-countryconfig 2.1.0-rc.d1b9774 → 2.1.0-rc.d235243

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/index.js +164 -4
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -13,6 +13,7 @@
13
13
 
14
14
  const path = require('path')
15
15
  const fs = require('fs')
16
+ const { execSync } = require('child_process')
16
17
  const readline = require('readline/promises')
17
18
  const degit = require('degit').default
18
19
 
@@ -21,6 +22,14 @@ const CORE_REPOSITORY = 'opencrvs/opencrvs-core'
21
22
  const COUNTRYCONFIG_TEMPLATE_REPOSITORY_SUBPATH =
22
23
  'packages/countryconfig-template'
23
24
 
25
+ const CORE_REPO_URL = 'https://github.com/' + CORE_REPOSITORY + '.git'
26
+ const INFRASTRUCTURE_REPO_URL =
27
+ 'https://github.com/' + INFRASTRUCTURE_REPOSITORY + '.git'
28
+
29
+ const { version } = JSON.parse(
30
+ fs.readFileSync(path.join(__dirname, 'package.json'), 'utf-8')
31
+ )
32
+
24
33
  function joinValues(values, separator) {
25
34
  return values
26
35
  .filter((value) => !!value)
@@ -28,19 +37,162 @@ function joinValues(values, separator) {
28
37
  .trim()
29
38
  }
30
39
 
40
+ function tagExists(repoUrl, tag) {
41
+ try {
42
+ execSync(
43
+ 'git ls-remote --exit-code --tags ' + repoUrl + ' refs/tags/' + tag,
44
+ { stdio: 'pipe' }
45
+ )
46
+ return true
47
+ } catch (err) {
48
+ return false
49
+ }
50
+ }
51
+
52
+ function branchExists(repoUrl, branch) {
53
+ try {
54
+ execSync(
55
+ 'git ls-remote --exit-code --heads ' + repoUrl + ' refs/heads/' + branch,
56
+ { stdio: 'pipe' }
57
+ )
58
+ return true
59
+ } catch (err) {
60
+ return false
61
+ }
62
+ }
63
+
64
+ /**
65
+ * Release tags (e.g. "v2.1.0"), highest first. Delegates the version-aware
66
+ * ordering to git itself rather than hand-parsing semver, then filters down
67
+ * to strict "vX.Y.Z" tags - `--sort=-version:refname` alone still leaves in
68
+ * non-release refs (e.g. "vtesting", "v2.0.0-beta") and peeled annotated-tag
69
+ * lines ("refs/tags/v2.0.0^{}").
70
+ */
71
+ function listReleaseTags(repoUrl) {
72
+ const output = execSync(
73
+ 'git ls-remote --tags --sort=-version:refname ' + repoUrl,
74
+ { encoding: 'utf-8' }
75
+ )
76
+
77
+ return output
78
+ .split('\n')
79
+ .map((line) => line.split('\t')[1])
80
+ .filter(Boolean)
81
+ .map((ref) => ref.replace('refs/tags/', ''))
82
+ .filter((tag) => /^v\d+\.\d+\.\d+$/.test(tag))
83
+ }
84
+
85
+ /**
86
+ * The highest release tag present in *both* repositories - used as the
87
+ * fallback when the version-specific tag can't be found in one or both, so
88
+ * scaffolding still lands on a real, matched release rather than develop.
89
+ */
90
+ function getLatestCommonReleaseTag() {
91
+ const infrastructureTags = new Set(listReleaseTags(INFRASTRUCTURE_REPO_URL))
92
+ return (
93
+ listReleaseTags(CORE_REPO_URL).find((tag) => infrastructureTags.has(tag)) ||
94
+ null
95
+ )
96
+ }
97
+
98
+ /**
99
+ * Resolves the ref that both repositories are cloned from. A single ref
100
+ * clones both, so every candidate must exist in *both* - a tag present in
101
+ * only one of them can't be used.
102
+ *
103
+ * The exact "v<version>" tag wins whenever it exists: it pins a commit, so
104
+ * a release, an explicit `@X.Y.Z` pin, or a blessed prerelease (npm `@beta`,
105
+ * published from that tag) reproduces however late it's scaffolded.
106
+ *
107
+ * Otherwise a prerelease falls back to a branch, since a rolling release
108
+ * candidate (npm `@next`) is never tagged: "release/X.Y.Z" of the base
109
+ * version when that release has been cut, otherwise develop. A release
110
+ * falls back to the highest tag common to both repositories - never a
111
+ * mismatched pairing of the two at different releases - or errors.
112
+ */
113
+ function resolveRef() {
114
+ const versionTag = 'v' + version
115
+ if (
116
+ tagExists(CORE_REPO_URL, versionTag) &&
117
+ tagExists(INFRASTRUCTURE_REPO_URL, versionTag)
118
+ ) {
119
+ return versionTag
120
+ }
121
+
122
+ if (version.includes('-')) {
123
+ const releaseBranch = 'release/' + version.split('-')[0]
124
+ if (
125
+ branchExists(CORE_REPO_URL, releaseBranch) &&
126
+ branchExists(INFRASTRUCTURE_REPO_URL, releaseBranch)
127
+ ) {
128
+ return releaseBranch
129
+ }
130
+ return 'develop'
131
+ }
132
+
133
+ const latestCommonTag = getLatestCommonReleaseTag()
134
+ if (latestCommonTag) {
135
+ console.warn(
136
+ '\nWarning: tag "' +
137
+ versionTag +
138
+ '" was not found in both repositories; falling back to the latest ' +
139
+ 'available release, ' +
140
+ latestCommonTag +
141
+ '.'
142
+ )
143
+ return latestCommonTag
144
+ }
145
+
146
+ console.error(
147
+ '\nError: no matching release tag was found in both the core and ' +
148
+ 'infrastructure repositories.'
149
+ )
150
+ process.exit(1)
151
+ }
152
+
31
153
  /**
32
154
  * Clones a repository from GitHub to a target directory.
33
155
  *
34
156
  * @param {*} param0 repository - The repository to clone (e.g., 'opencrvs/opencrvs-core').
35
157
  * @param {*} param0 repositorySubPath - The subpath within the repository to clone (optional). Otherwise the entire repository will be cloned.
36
158
  * @param {*} param0 branch - The branch to clone (optional). Defaults to the default branch if not specified.
159
+ * @param {*} param0 keepHistory - Keep the repository's git history instead of degit's usual history-free copy (optional, defaults to false). The cloned "origin" remote is replaced with "upstream", leaving the directory ready for the user to add their own fork/repo as "origin". Not supported together with repositorySubPath, since a plain git clone can't fetch a single subdirectory.
37
160
  *
38
161
  * @param {*} targetDir - The target directory where the repository will be cloned.
39
162
  */
40
163
  async function cloneRepository(
41
- { repository, repositorySubPath, branch },
164
+ { repository, repositorySubPath, branch, keepHistory = false },
42
165
  targetDir
43
166
  ) {
167
+ if (keepHistory && repositorySubPath) {
168
+ throw new Error(
169
+ 'cloneRepository: keepHistory is not supported together with repositorySubPath.'
170
+ )
171
+ }
172
+
173
+ if (keepHistory) {
174
+ const repoUrl = `https://github.com/${repository}.git`
175
+ console.log(
176
+ `Cloning repository from ${repoUrl}#${branch} to ${targetDir}...`
177
+ )
178
+
179
+ execSync('git clone --branch ' + branch + ' ' + repoUrl + ' ' + targetDir, {
180
+ stdio: 'inherit'
181
+ })
182
+
183
+ console.log(
184
+ `Copied files from ${repoUrl}#${branch} to ${targetDir} succesfully.`
185
+ )
186
+
187
+ console.log(`Replacing 'origin' remote with 'upstream' in ${targetDir}...`)
188
+ execSync('git remote remove origin', { cwd: targetDir, stdio: 'inherit' })
189
+ execSync('git remote add upstream ' + repoUrl, {
190
+ cwd: targetDir,
191
+ stdio: 'inherit'
192
+ })
193
+ return
194
+ }
195
+
44
196
  const repositoryPath = joinValues([repository, repositorySubPath], '/')
45
197
  const fullPath = joinValues([repositoryPath, branch], '#')
46
198
 
@@ -271,6 +423,8 @@ async function main() {
271
423
  ensureTargetDirectoryDoesNotExist(countryconfigDirName)
272
424
  ensureTargetDirectoryDoesNotExist(infrastructureDirName)
273
425
 
426
+ const ref = resolveRef()
427
+
274
428
  // Gather all answers up front so the operator isn't interrupted mid-clone.
275
429
  const organisation = await promptOrganisation()
276
430
  const countryCode = await promptCountryCode()
@@ -280,7 +434,8 @@ async function main() {
280
434
  await cloneRepository(
281
435
  {
282
436
  repository: CORE_REPOSITORY,
283
- repositorySubPath: COUNTRYCONFIG_TEMPLATE_REPOSITORY_SUBPATH
437
+ repositorySubPath: COUNTRYCONFIG_TEMPLATE_REPOSITORY_SUBPATH,
438
+ branch: ref
284
439
  },
285
440
  countryconfigTargetPath
286
441
  )
@@ -293,7 +448,7 @@ async function main() {
293
448
 
294
449
  try {
295
450
  await cloneRepository(
296
- { repository: INFRASTRUCTURE_REPOSITORY },
451
+ { repository: INFRASTRUCTURE_REPOSITORY, branch: ref, keepHistory: true },
297
452
  infrastructureTargetPath
298
453
  )
299
454
  } catch (err) {
@@ -319,7 +474,12 @@ async function main() {
319
474
  console.log(' tilt up\n')
320
475
  console.log('To get started with the infrastructure:\n')
321
476
  console.log(' cd ' + infrastructureDirName)
322
- console.log(' git init\n')
477
+ console.log(' git remote add origin <your-infrastructure-repo-url>')
478
+ console.log(' git push -u origin ' + ref + '\n')
479
+ console.log(
480
+ 'The "upstream" remote points at the official infrastructure repository, ' +
481
+ 'so you can pull future releases with `git fetch upstream`.\n'
482
+ )
323
483
  }
324
484
 
325
485
  main()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opencrvs/create-countryconfig",
3
- "version": "2.1.0-rc.d1b9774",
3
+ "version": "2.1.0-rc.d235243",
4
4
  "description": "Scaffold a new OpenCRVS country configuration",
5
5
  "bin": {
6
6
  "create-countryconfig": "./index.js"