@robylon/react-native-sdk 2.0.18 → 2.0.19-staging.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@robylon/react-native-sdk",
3
- "version": "2.0.18",
3
+ "version": "2.0.19-staging.0",
4
4
  "description": "React Native SDK for Robylon",
5
5
  "main": "lib/commonjs/index.js",
6
6
  "module": "lib/module/index.js",
@@ -22,8 +22,12 @@
22
22
  "version:staging": "npm version prerelease --preid staging",
23
23
  "version:production": "npm version patch",
24
24
  "publish:dev": "npm run clean && npm run prebuild:dev && npm run version:dev && npm run build:dev && npm publish --tag dev",
25
- "publish:staging": "npm run clean && npm run prebuild:staging && npm run version:staging && npm run build:staging && npm publish --tag staging",
26
- "publish:production": "npm run clean && npm run prebuild:production && npm run version:production && npm run build:production && npm publish --tag latest"
25
+ "publish:staging": "node scripts/validate-publish.js staging && npm version $(node scripts/get-next-version.js staging) && npm publish --tag staging",
26
+ "publish:production": "node scripts/validate-publish.js production && npm version $(node scripts/get-next-version.js production) && npm publish",
27
+ "publish:hotfix": "node scripts/validate-publish.js hotfix && npm version $(node scripts/get-next-version.js hotfix) && npm publish --tag hotfix",
28
+ "postinstall": "node scripts/setup-git-hooks.js",
29
+ "branch": "node scripts/create-branch.js",
30
+ "tag": "node scripts/create-version-tag.js"
27
31
  },
28
32
  "peerDependencies": {
29
33
  "react": "*",
@@ -37,10 +41,12 @@
37
41
  "babel-plugin-inline-dotenv": "^1.7.0",
38
42
  "babel-plugin-transform-inline-environment-variables": "^0.4.4",
39
43
  "env-cmd": "^10.1.0",
44
+ "husky": "^8.0.3",
40
45
  "react": "^18.0.0",
41
- "react-native": "^0.70.0",
46
+ "react-native": "^0.76.5",
42
47
  "react-native-builder-bob": "^0.20.0",
43
48
  "rimraf": "^6.0.1",
49
+ "semver": "^7.5.4",
44
50
  "typescript": "^4.5.2"
45
51
  },
46
52
  "react-native-builder-bob": {
@@ -70,5 +76,10 @@
70
76
  },
71
77
  "author": "",
72
78
  "license": "ISC",
73
- "dependencies": {}
79
+ "husky": {
80
+ "hooks": {
81
+ "commit-msg": "sh scripts/validate-branch-name.sh",
82
+ "post-checkout": "node scripts/prevent-direct-branch.js"
83
+ }
84
+ }
74
85
  }
@@ -0,0 +1,560 @@
1
+ const { execSync } = require("child_process");
2
+ const readline = require("readline");
3
+ const semver = require("semver");
4
+
5
+ const rl = readline.createInterface({
6
+ input: process.stdin,
7
+ output: process.stdout,
8
+ });
9
+
10
+ // Calculate the next patch version
11
+ const getNextPatchVersion = (currentVersion) => {
12
+ const versionParts = currentVersion.split(".");
13
+ const nextPatch = parseInt(versionParts[2]) + 1;
14
+ return `${versionParts[0]}.${versionParts[1]}.${nextPatch}`;
15
+ };
16
+
17
+ // Validate branch name format
18
+ const isValidFeatureName = (name) => /^[a-z0-9-]+$/.test(name);
19
+
20
+ // Get the latest version from remote main
21
+ const getLatestMainVersion = () => {
22
+ try {
23
+ // Fetch latest from remote
24
+ execSync("git fetch origin main --quiet");
25
+
26
+ // Get package.json content from remote main
27
+ const remotePackageJson = execSync(
28
+ "git show origin/main:package.json"
29
+ ).toString();
30
+
31
+ return JSON.parse(remotePackageJson).version;
32
+ } catch (error) {
33
+ throw new Error(
34
+ "Failed to get version from remote main. Ensure you have internet connection and repository access."
35
+ );
36
+ }
37
+ };
38
+
39
+ // Get target version for new branches
40
+ const getTargetVersion = () => {
41
+ try {
42
+ // Get current branch
43
+ const currentBranch = execSync("git rev-parse --abbrev-ref HEAD")
44
+ .toString()
45
+ .trim();
46
+
47
+ // If we're on a release branch, use its version for new features
48
+ if (currentBranch.startsWith("release/v")) {
49
+ return currentBranch.split("/v")[1];
50
+ }
51
+
52
+ // Otherwise, use next version from main
53
+ const mainVersion = getLatestMainVersion();
54
+ return getNextPatchVersion(mainVersion);
55
+ } catch (error) {
56
+ throw new Error("Failed to determine target version");
57
+ }
58
+ };
59
+
60
+ // Validate that we're not creating conflicting branches
61
+ const validateVersionAvailability = async (targetVersion) => {
62
+ try {
63
+ // Get all remote branches
64
+ const remoteBranches = execSync("git ls-remote --heads origin")
65
+ .toString()
66
+ .split("\n")
67
+ .filter(Boolean)
68
+ .map((line) => line.split("/").slice(3).join("/"));
69
+
70
+ // Check for existing feature branches with same version
71
+ const conflictingBranches = remoteBranches.filter((branch) =>
72
+ branch.startsWith(`feature/v${targetVersion}/`)
73
+ );
74
+
75
+ if (conflictingBranches.length > 0) {
76
+ console.log(
77
+ "\n⚠️ Warning: The following branches already exist for version",
78
+ targetVersion
79
+ );
80
+ conflictingBranches.forEach((branch) => console.log(` - ${branch}`));
81
+
82
+ const proceed = await new Promise((resolve) =>
83
+ rl.question("\nDo you want to proceed anyway? (y/N): ", resolve)
84
+ );
85
+
86
+ if (proceed.toLowerCase() !== "y") {
87
+ throw new Error("Operation cancelled by user");
88
+ }
89
+ }
90
+ } catch (error) {
91
+ if (error.message === "Operation cancelled by user") {
92
+ throw error;
93
+ }
94
+ throw new Error("Failed to validate version availability");
95
+ }
96
+ };
97
+
98
+ // Calculate the next version based on type
99
+ const getNextVersion = (currentVersion, type) => {
100
+ // Strip any pre-release tags (e.g., -staging.0) and split version
101
+ const baseVersion = currentVersion.split("-")[0];
102
+ const [major, minor, patch] = baseVersion.split(".").map(Number);
103
+
104
+ switch (type) {
105
+ case "major":
106
+ return `${major + 1}.0.0`;
107
+ case "minor":
108
+ return `${major}.${minor + 1}.0`;
109
+ case "patch":
110
+ return `${major}.${minor}.${patch + 1}`;
111
+ default:
112
+ throw new Error("Invalid version type");
113
+ }
114
+ };
115
+
116
+ // Get unmerged release branches
117
+ const getUnmergedReleaseBranches = () => {
118
+ try {
119
+ return execSync("git branch -r")
120
+ .toString()
121
+ .split("\n")
122
+ .filter((branch) => branch.includes("origin/release/v"))
123
+ .map((branch) => branch.trim())
124
+ .filter((branch) => {
125
+ const version = branch.split("/v")[1];
126
+ return semver.gt(version, getLatestMainVersion());
127
+ });
128
+ } catch (error) {
129
+ return [];
130
+ }
131
+ };
132
+
133
+ // Get unmerged feature branches for a version
134
+ const getUnmergedFeatureBranches = (version) => {
135
+ return execSync("git branch -r")
136
+ .toString()
137
+ .split("\n")
138
+ .filter((branch) => branch.includes(`feature/v${version}/`))
139
+ .map((branch) => branch.trim());
140
+ };
141
+
142
+ // Get repository URL for PR creation
143
+ const getRepoUrl = () => {
144
+ const remoteUrl = execSync("git remote get-url origin").toString().trim();
145
+ return remoteUrl
146
+ .replace(/\.git$/, "")
147
+ .replace("git@github.com:", "https://github.com/");
148
+ };
149
+
150
+ const createBranch = async () => {
151
+ try {
152
+ // Set environment variable to allow branch creation
153
+ process.env.BRANCH_CREATION_ALLOWED = "true";
154
+
155
+ // Ensure we have latest changes
156
+ console.log("\n📡 Fetching latest changes...");
157
+ execSync("git fetch origin --quiet");
158
+
159
+ // Get target version for new branches
160
+ const targetVersion = getTargetVersion();
161
+ const localVersion = require("../package.json").version;
162
+ const remoteMainVersion = getLatestMainVersion();
163
+
164
+ // Only check if we're on main
165
+ const currentBranch = execSync("git rev-parse --abbrev-ref HEAD")
166
+ .toString()
167
+ .trim();
168
+ if (currentBranch === "main") {
169
+ if (semver.lt(localVersion, remoteMainVersion)) {
170
+ throw new Error(
171
+ `Your local branch (${localVersion}) is behind remote main (${remoteMainVersion}). ` +
172
+ `Please pull latest changes first: git pull origin main`
173
+ );
174
+ }
175
+ }
176
+
177
+ console.log("\nCurrent version:", localVersion);
178
+
179
+ console.log("\nSelect branch type:");
180
+ console.log("1. Feature branch");
181
+ console.log("2. Release branch");
182
+ console.log("3. Hotfix branch");
183
+
184
+ const answer = await new Promise((resolve) =>
185
+ rl.question("\nEnter your choice (1, 2 or 3): ", resolve)
186
+ );
187
+
188
+ if (answer === "1") {
189
+ // Feature branch
190
+ await validateVersionAvailability(targetVersion);
191
+
192
+ const featureName = await new Promise((resolve) =>
193
+ rl.question("\nEnter feature name (use-kebab-case): ", resolve)
194
+ );
195
+
196
+ if (!isValidFeatureName(featureName)) {
197
+ throw new Error(
198
+ "Invalid feature name. Use kebab-case (e.g., 'add-chat-widget')"
199
+ );
200
+ }
201
+
202
+ const branchName = `feature/v${targetVersion}/${featureName}`;
203
+ execSync(`git checkout -b ${branchName}`);
204
+ console.log(`\n✅ Successfully created feature branch: ${branchName}`);
205
+ } else if (answer === "2") {
206
+ // Release branch
207
+ console.log("\nCreating release branch for next version...");
208
+
209
+ const conflictedBranches = [];
210
+
211
+ // Check for unmerged release branches first
212
+ const unmergedReleases = getUnmergedReleaseBranches();
213
+ if (unmergedReleases.length > 0) {
214
+ console.log("\n⚠️ Unmerged release branches found:");
215
+ unmergedReleases.forEach((branch, index) =>
216
+ console.log(`${index + 1}. ${branch}`)
217
+ );
218
+
219
+ const proceed = await new Promise((resolve) =>
220
+ rl.question(
221
+ "\nWould you like to:\n" +
222
+ "1. Add features to existing release\n" +
223
+ "2. Cancel operation\n" +
224
+ "\nEnter your choice (1 or 2): ",
225
+ resolve
226
+ )
227
+ );
228
+
229
+ if (proceed === "1") {
230
+ const releaseChoice = await new Promise((resolve) =>
231
+ rl.question("\nSelect release branch number: ", resolve)
232
+ );
233
+
234
+ const selectedRelease = unmergedReleases[parseInt(releaseChoice) - 1];
235
+ if (!selectedRelease) {
236
+ throw new Error("Invalid release branch selection");
237
+ }
238
+
239
+ const releaseVersion = selectedRelease.split("/v")[1];
240
+ const unmergedFeatures = getUnmergedFeatureBranches(releaseVersion);
241
+
242
+ if (unmergedFeatures.length === 0) {
243
+ console.log(
244
+ "\nNo unmerged feature branches found for this version."
245
+ );
246
+ return;
247
+ }
248
+
249
+ console.log("\nUnmerged feature branches:");
250
+ unmergedFeatures.forEach((branch, index) =>
251
+ console.log(`${index + 1}. ${branch}`)
252
+ );
253
+
254
+ const featureChoice = await new Promise((resolve) =>
255
+ rl.question(
256
+ "\nSelect features to merge (comma-separated numbers or 'all'): ",
257
+ resolve
258
+ )
259
+ );
260
+
261
+ // Checkout the release branch
262
+ execSync(`git fetch origin ${selectedRelease}`);
263
+ execSync(
264
+ `git checkout -b ${selectedRelease.split("origin/")[1]} ${selectedRelease}`
265
+ );
266
+
267
+ // Merge selected features
268
+ const selectedFeatures =
269
+ featureChoice.toLowerCase() === "all"
270
+ ? unmergedFeatures
271
+ : featureChoice
272
+ .split(",")
273
+ .map((num) => unmergedFeatures[parseInt(num.trim()) - 1])
274
+ .filter(Boolean);
275
+
276
+ for (const branch of selectedFeatures) {
277
+ console.log(`\nMerging ${branch}...`);
278
+ try {
279
+ execSync(
280
+ `git merge ${branch} --no-ff -m "Merge ${branch} into ${branchName}"`
281
+ );
282
+ } catch (error) {
283
+ console.log(`\n⚠️ Merge conflict detected with ${branch}!`);
284
+ execSync("git merge --abort");
285
+ console.log(`\n⏩ Skipping ${branch} due to conflicts`);
286
+ conflictedBranches.push(branch);
287
+ }
288
+ }
289
+
290
+ // After all merges, show summary of conflicted branches
291
+ if (conflictedBranches.length > 0) {
292
+ const repoUrl = getRepoUrl();
293
+ console.log("\n🚨 MERGE CONFLICTS DETECTED 🚨");
294
+ console.log("=====================================");
295
+ conflictedBranches.forEach((branch) => {
296
+ const prUrl = `${repoUrl}/compare/${branchName}...${branch}?expand=1`;
297
+ console.log(`\n❌ ${branch}`);
298
+ console.log(`🔗 Create PR: ${prUrl}`);
299
+ });
300
+ console.log(
301
+ "\n⚠️ ACTION REQUIRED: Please resolve conflicts and merge these branches manually ⚠️"
302
+ );
303
+ }
304
+
305
+ // Show success message with merged/unmerged counts
306
+ const mergedCount =
307
+ selectedFeatures.length - conflictedBranches.length;
308
+ console.log(
309
+ `\n✅ Successfully merged ${mergedCount}/${selectedFeatures.length} branches`
310
+ );
311
+ if (conflictedBranches.length > 0) {
312
+ console.log(
313
+ `\n🚨 ATTENTION: ${conflictedBranches.length} branches require manual conflict resolution 🚨`
314
+ );
315
+ }
316
+
317
+ return;
318
+ }
319
+
320
+ throw new Error(
321
+ "Operation cancelled - please merge existing release first"
322
+ );
323
+ }
324
+
325
+ // Ask for version type
326
+ console.log("\nSelect version type:");
327
+ console.log(`1. Major (${getNextVersion(localVersion, "major")})`);
328
+ console.log(`2. Minor (${getNextVersion(localVersion, "minor")})`);
329
+ console.log(`3. Patch (${getNextVersion(localVersion, "patch")})`);
330
+
331
+ const versionType = await new Promise((resolve) =>
332
+ rl.question("\nEnter your choice (1, 2 or 3): ", resolve)
333
+ );
334
+
335
+ let releaseVersion;
336
+ switch (versionType) {
337
+ case "1":
338
+ releaseVersion = getNextVersion(localVersion, "major");
339
+ break;
340
+ case "2":
341
+ releaseVersion = getNextVersion(localVersion, "minor");
342
+ break;
343
+ case "3":
344
+ releaseVersion = getNextVersion(localVersion, "patch");
345
+ break;
346
+ default:
347
+ throw new Error("Invalid version type selection");
348
+ }
349
+
350
+ console.log(`\nUpdating package.json version to ${releaseVersion}...`);
351
+
352
+ // Create release branch
353
+ const branchName = `release/v${releaseVersion}`;
354
+
355
+ // Check if branch exists remotely
356
+ const remoteBranches = execSync("git ls-remote --heads origin")
357
+ .toString()
358
+ .split("\n")
359
+ .filter(Boolean)
360
+ .map((line) => line.split("/").slice(2).join("/"));
361
+
362
+ const branchExists = remoteBranches.includes(branchName);
363
+
364
+ if (branchExists) {
365
+ const proceed = await new Promise((resolve) =>
366
+ rl.question(
367
+ "\n⚠️ Release branch already exists remotely. Do you want to:\n" +
368
+ "1. Create new branch with force push (overwrite remote)\n" +
369
+ "2. Checkout existing branch and continue\n" +
370
+ "3. Cancel operation\n" +
371
+ "\nEnter your choice (1, 2 or 3): ",
372
+ resolve
373
+ )
374
+ );
375
+
376
+ switch (proceed) {
377
+ case "1":
378
+ // Continue with force push
379
+ break;
380
+ case "2":
381
+ execSync(`git fetch origin ${branchName}`);
382
+ execSync(`git checkout -b ${branchName} origin/${branchName}`);
383
+ console.log(`\n✅ Checked out existing branch: ${branchName}`);
384
+ return;
385
+ default:
386
+ throw new Error("Operation cancelled by user");
387
+ }
388
+ }
389
+
390
+ execSync(`git checkout -b ${branchName}`);
391
+
392
+ // Update version and commit
393
+ execSync(`npm version ${releaseVersion} --no-git-tag-version`);
394
+ execSync(`git add package.json package-lock.json`);
395
+ execSync(`git commit -m "chore: update version to ${releaseVersion}"`);
396
+
397
+ // Set up tracking and push to remote
398
+ try {
399
+ console.log("\n📡 Pushing to remote...");
400
+ execSync(
401
+ `git push -u origin ${branchName}${branchExists ? " --force" : ""}`
402
+ );
403
+ } catch (error) {
404
+ throw new Error(
405
+ `Failed to push to remote. Please check your permissions and try again.\nError: ${error.message}`
406
+ );
407
+ }
408
+
409
+ // Get all feature branches for next version
410
+ const featureBranches = execSync("git branch -r")
411
+ .toString()
412
+ .split("\n")
413
+ .filter((branch) => branch.includes(`feature/v${releaseVersion}/`))
414
+ .map((branch) => branch.trim());
415
+
416
+ if (featureBranches.length === 0) {
417
+ console.log("\n⚠️ No feature branches found for this version.");
418
+ const proceed = await new Promise((resolve) =>
419
+ rl.question("\nContinue without feature branches? (y/N): ", resolve)
420
+ );
421
+ if (proceed.toLowerCase() !== "y") {
422
+ throw new Error("Operation cancelled by user");
423
+ }
424
+ } else {
425
+ console.log("\nAvailable feature branches:");
426
+ featureBranches.forEach((branch, index) =>
427
+ console.log(`${index + 1}. ${branch}`)
428
+ );
429
+
430
+ const featureChoice = await new Promise((resolve) =>
431
+ rl.question(
432
+ "\nSelect features to include (comma-separated numbers or 'all' or 'none'): ",
433
+ resolve
434
+ )
435
+ );
436
+
437
+ // Treat empty input as "none"
438
+ const choice = featureChoice.trim().toLowerCase();
439
+ const isNoneOrEmpty = !choice || choice === "none";
440
+
441
+ if (!isNoneOrEmpty) {
442
+ const selectedFeatures =
443
+ choice === "all"
444
+ ? featureBranches
445
+ : choice
446
+ .split(",")
447
+ .map((num) => featureBranches[parseInt(num.trim()) - 1])
448
+ .filter(Boolean);
449
+
450
+ // Validate that we have valid selections
451
+ if (selectedFeatures.length === 0) {
452
+ throw new Error(
453
+ "No valid feature branches selected. Please try again"
454
+ );
455
+ }
456
+
457
+ // Merge selected feature branches
458
+ for (const branch of selectedFeatures) {
459
+ console.log(`\nMerging ${branch}...`);
460
+ try {
461
+ execSync(
462
+ `git merge ${branch} --no-ff -m "Merge ${branch} into ${branchName}"`
463
+ );
464
+ } catch (error) {
465
+ console.log(`\n⚠️ Merge conflict detected with ${branch}!`);
466
+ execSync("git merge --abort");
467
+ console.log(`\n⏩ Skipping ${branch} due to conflicts`);
468
+ conflictedBranches.push(branch);
469
+ }
470
+ }
471
+
472
+ // After all merges, show summary of conflicted branches
473
+ if (conflictedBranches.length > 0) {
474
+ const repoUrl = getRepoUrl();
475
+ console.log("\n🚨 MERGE CONFLICTS DETECTED 🚨");
476
+ console.log("=====================================");
477
+ conflictedBranches.forEach((branch) => {
478
+ const prUrl = `${repoUrl}/compare/${branchName}...${branch}?expand=1`;
479
+ console.log(`\n❌ ${branch}`);
480
+ console.log(`🔗 Create PR: ${prUrl}`);
481
+ });
482
+ console.log(
483
+ "\n⚠️ ACTION REQUIRED: Please resolve conflicts and merge these branches manually ⚠️"
484
+ );
485
+ }
486
+
487
+ // Show success message with merged/unmerged counts
488
+ const mergedCount =
489
+ selectedFeatures.length - conflictedBranches.length;
490
+ console.log(
491
+ `\n✅ Successfully merged ${mergedCount}/${selectedFeatures.length} branches`
492
+ );
493
+ if (conflictedBranches.length > 0) {
494
+ console.log(
495
+ `\n🚨 ATTENTION: ${conflictedBranches.length} branches require manual conflict resolution 🚨`
496
+ );
497
+ }
498
+ } else {
499
+ console.log("\n📝 Creating release branch without features");
500
+ }
501
+ }
502
+
503
+ // Push all changes to remote
504
+ try {
505
+ console.log("\n📡 Pushing final changes to remote...");
506
+ execSync(`git push origin ${branchName}`);
507
+ } catch (error) {
508
+ throw new Error(
509
+ `Failed to push final changes. Please push manually.\nError: ${error.message}`
510
+ );
511
+ }
512
+
513
+ console.log(`\n✅ Successfully created release branch: ${branchName}`);
514
+ console.log("\nNext steps:");
515
+ console.log("1. Review and test the changes");
516
+ console.log("2. Run 'npm run publish:production' when ready to release");
517
+ } else if (answer === "3") {
518
+ // Hotfix branch
519
+ console.log("\nEnter the version to hotfix (e.g., 1.1.14):");
520
+ const targetVersion = await new Promise((resolve) =>
521
+ rl.question("\nVersion: ", resolve)
522
+ );
523
+
524
+ // Validate version format
525
+ if (!/^\d+\.\d+\.\d+$/.test(targetVersion)) {
526
+ throw new Error("Invalid version format. Use semver (e.g., 1.1.14)");
527
+ }
528
+
529
+ // Get patch number
530
+ console.log("\nEnter patch number (e.g., 1 for first patch):");
531
+ const patchNumber = await new Promise((resolve) =>
532
+ rl.question("\nPatch number: ", resolve)
533
+ );
534
+
535
+ if (!/^\d+$/.test(patchNumber)) {
536
+ throw new Error("Invalid patch number. Use a number (e.g., 1)");
537
+ }
538
+
539
+ const branchName = `hotfix/v${targetVersion}-patch.${patchNumber}`;
540
+
541
+ // Checkout the tag first
542
+ execSync(`git fetch --all --tags`);
543
+ execSync(`git checkout v${targetVersion}`);
544
+ execSync(`git checkout -b ${branchName}`);
545
+
546
+ console.log(`\n✅ Successfully created hotfix branch: ${branchName}`);
547
+ } else {
548
+ throw new Error("Invalid choice. Please select 1, 2 or 3.");
549
+ }
550
+ } catch (error) {
551
+ console.error("\n❌ Error:", error.message);
552
+ } finally {
553
+ // Clear the environment variable
554
+ delete process.env.BRANCH_CREATION_ALLOWED;
555
+
556
+ rl.close();
557
+ }
558
+ };
559
+
560
+ createBranch();
@@ -0,0 +1,29 @@
1
+ const { execSync } = require("child_process");
2
+
3
+ const createVersionTag = () => {
4
+ try {
5
+ const { version } = require("../package.json");
6
+ const tagName = `v${version}`;
7
+
8
+ // Check if tag exists
9
+ try {
10
+ execSync(`git tag -l "${tagName}"`).toString().trim();
11
+ console.log(`✅ Tag ${tagName} already exists, skipping tag creation`);
12
+ return;
13
+ } catch (error) {
14
+ // Tag doesn't exist, create it
15
+ try {
16
+ execSync(`git tag -a ${tagName} -m "Release: ${tagName}"`);
17
+ execSync(`git push origin ${tagName}`);
18
+ console.log(`✅ Successfully created and pushed tag: ${tagName}`);
19
+ } catch (tagError) {
20
+ throw new Error(`Failed to create or push tag: ${tagError.message}`);
21
+ }
22
+ }
23
+ } catch (error) {
24
+ console.error("\n❌ Error creating tag:", error.message);
25
+ process.exit(1);
26
+ }
27
+ };
28
+
29
+ createVersionTag();
@@ -0,0 +1,29 @@
1
+ const semver = require("semver");
2
+ const { execSync } = require("child_process");
3
+
4
+ function getNextVersion(publishType) {
5
+ const { version } = require("../package.json");
6
+
7
+ switch (publishType) {
8
+ case "staging":
9
+ return semver.inc(version, "prerelease", "staging");
10
+ case "hotfix":
11
+ return semver.inc(version, "prerelease", "patch");
12
+ case "production":
13
+ return semver.inc(version, "patch");
14
+ default:
15
+ throw new Error(`Invalid publish type: ${publishType}`);
16
+ }
17
+ }
18
+
19
+ // If script is run directly
20
+ if (require.main === module) {
21
+ const publishType = process.argv[2];
22
+ if (!publishType) {
23
+ console.error("Please provide a publish type (staging|production|hotfix)");
24
+ process.exit(1);
25
+ }
26
+ console.log(getNextVersion(publishType));
27
+ }
28
+
29
+ module.exports = getNextVersion;
@@ -0,0 +1,32 @@
1
+ const { execSync } = require("child_process");
2
+ const fs = require("fs");
3
+ const path = require("path");
4
+
5
+ // Initialize husky
6
+ execSync("npx husky install");
7
+
8
+ // Create .husky directory if it doesn't exist
9
+ const huskyDir = path.join(__dirname, "../.husky");
10
+ if (!fs.existsSync(huskyDir)) {
11
+ fs.mkdirSync(huskyDir, { recursive: true });
12
+ }
13
+
14
+ // Create commit-msg hook
15
+ const hookContent = `#!/bin/sh
16
+
17
+ sh scripts/validate-branch-name.sh
18
+ `;
19
+
20
+ fs.writeFileSync(path.join(huskyDir, "commit-msg"), hookContent);
21
+ fs.chmodSync(path.join(huskyDir, "commit-msg"), "755");
22
+
23
+ // Create post-checkout hook to prevent direct branch creation
24
+ const postCheckoutContent = `#!/bin/sh
25
+
26
+ node scripts/prevent-direct-branch.js
27
+ `;
28
+
29
+ fs.writeFileSync(path.join(huskyDir, "post-checkout"), postCheckoutContent);
30
+ fs.chmodSync(path.join(huskyDir, "post-checkout"), "755");
31
+
32
+ console.log("Husky hooks installed successfully!");
@@ -0,0 +1,37 @@
1
+ const { execSync } = require("child_process");
2
+
3
+ // Get command line arguments passed by Git
4
+ // In post-checkout hook, Git passes 3 arguments:
5
+ // 1. Previous HEAD ref
6
+ // 2. New HEAD ref
7
+ // 3. Flag (1 for branch checkout, 0 for file checkout)
8
+ const [prevHead, newHead, checkoutFlag] = process.argv.slice(2);
9
+
10
+ try {
11
+ // Get the current branch name
12
+ const currentBranch = execSync("git rev-parse --abbrev-ref HEAD")
13
+ .toString()
14
+ .trim();
15
+
16
+ // Only check for branch creation (when prevHead and newHead are different and checkoutFlag is 1)
17
+ const isBranchCreation = prevHead !== newHead && checkoutFlag === "1";
18
+
19
+ // Check if this was a direct branch creation
20
+ if (
21
+ isBranchCreation && // Only check actual branch creation
22
+ !currentBranch.includes("temp-version-update")
23
+ ) {
24
+ // If we moved from one branch to a new one, it was likely created directly
25
+ if (!process.env.BRANCH_CREATION_ALLOWED) {
26
+ console.error("\n❌ Direct branch creation is not allowed.");
27
+ console.error("Please use: npm run branch");
28
+ console.error(
29
+ "This ensures proper version control and naming conventions."
30
+ );
31
+ process.exit(1);
32
+ }
33
+ }
34
+ } catch (error) {
35
+ // If we can't determine, let it pass
36
+ process.exit(0);
37
+ }
@@ -0,0 +1,13 @@
1
+ const { execSync } = require("child_process");
2
+
3
+ // Get current branch name
4
+ const branch = execSync("git rev-parse --abbrev-ref HEAD").toString().trim();
5
+
6
+ // If we're on a release branch, don't increment version
7
+ if (branch.startsWith("release/v")) {
8
+ console.log("On release branch - skipping version increment");
9
+ process.exit(0);
10
+ } else {
11
+ // For other branches, increment patch version
12
+ execSync("npm version patch", { stdio: "inherit" });
13
+ }
@@ -0,0 +1,77 @@
1
+ const { execSync } = require("child_process");
2
+ const readline = require("readline");
3
+
4
+ const rl = readline.createInterface({
5
+ input: process.stdin,
6
+ output: process.stdout,
7
+ });
8
+
9
+ const release = async () => {
10
+ try {
11
+ // 1. Validate current branch is a release branch
12
+ const currentBranch = execSync("git rev-parse --abbrev-ref HEAD")
13
+ .toString()
14
+ .trim();
15
+
16
+ if (!currentBranch.startsWith("release/v")) {
17
+ throw new Error(
18
+ "Release process must be started from a release branch.\n" +
19
+ `Current branch: ${currentBranch}\n` +
20
+ "Expected format: release/v{version}"
21
+ );
22
+ }
23
+
24
+ // 2. Check if version is already published
25
+ const version = currentBranch.split("/v")[1];
26
+ const publishedVersions = execSync(
27
+ "npm view @robylon/web-react-sdk versions --json"
28
+ ).toString();
29
+
30
+ if (publishedVersions.includes(version)) {
31
+ throw new Error(
32
+ `Version ${version} is already published to npm.\n` +
33
+ "Please create a new release branch with a different version."
34
+ );
35
+ }
36
+
37
+ // 3. Confirm release process
38
+ console.log("\n📦 Starting release process for version", version);
39
+ console.log("\nThis will:");
40
+ console.log("1. Publish to npm");
41
+ console.log("2. Create git tag");
42
+ console.log("3. Merge to main");
43
+
44
+ const proceed = await new Promise((resolve) =>
45
+ rl.question("\nProceed with release? (y/N): ", resolve)
46
+ );
47
+
48
+ if (proceed.toLowerCase() !== "y") {
49
+ throw new Error("Release cancelled by user");
50
+ }
51
+
52
+ // 4. Run publish
53
+ console.log("\n🚀 Publishing to npm...");
54
+ execSync("npm run publish:production", { stdio: "inherit" });
55
+
56
+ // 5. Merge to main
57
+ console.log("\n🔄 Merging to main...");
58
+ execSync("git checkout main");
59
+ execSync("git pull origin main");
60
+ execSync(
61
+ `git merge ${currentBranch} --no-ff -m "chore: release version ${version}"`
62
+ );
63
+ execSync("git push origin main");
64
+
65
+ console.log("\n✅ Release completed successfully!");
66
+ console.log(`Version ${version} is now available on npm`);
67
+ console.log("Changes have been merged to main");
68
+ } catch (error) {
69
+ console.error("\n❌ Release failed:");
70
+ console.error(error.message);
71
+ process.exit(1);
72
+ } finally {
73
+ rl.close();
74
+ }
75
+ };
76
+
77
+ release();
@@ -0,0 +1,18 @@
1
+ const fs = require("fs");
2
+ const path = require("path");
3
+ const { execSync } = require("child_process");
4
+
5
+ // Create .git/hooks directory if it doesn't exist
6
+ const hooksDir = path.join(__dirname, "../.git/hooks");
7
+ if (!fs.existsSync(hooksDir)) {
8
+ fs.mkdirSync(hooksDir, { recursive: true });
9
+ }
10
+
11
+ // Copy the validate-branch-name script
12
+ const sourcePath = path.join(__dirname, "validate-branch-name.sh");
13
+ const targetPath = path.join(hooksDir, "commit-msg");
14
+
15
+ fs.copyFileSync(sourcePath, targetPath);
16
+ fs.chmodSync(targetPath, "755");
17
+
18
+ console.log("Git hooks installed successfully!");
@@ -0,0 +1,65 @@
1
+ #!/bin/bash
2
+
3
+ echo "DEBUG: Script executed from: $0"
4
+ echo "DEBUG: PWD: $(pwd)"
5
+
6
+ # Get the full version including staging suffix if present
7
+ FULL_VERSION=$(node -p "require('./package.json').version")
8
+
9
+ # Check if this is a staging version before any other operations
10
+ if [[ $FULL_VERSION == *"-staging."* ]]; then
11
+ echo "Staging version detected. Skipping branch name validation."
12
+ exit 0
13
+ fi
14
+
15
+ # Get base version without pre-release tags (e.g., 1.1.14 from 1.1.14-staging.0)
16
+ BASE_VERSION=$(echo $FULL_VERSION | cut -d'-' -f1)
17
+
18
+ # Get the branch name
19
+ BRANCH_NAME=$(git rev-parse --abbrev-ref HEAD)
20
+
21
+ # Get latest published version from npm
22
+ LATEST_VERSION=$(npm view @robylon/web-react-sdk version 2>/dev/null || echo "0.0.0")
23
+
24
+ # Regular expression for valid branch names
25
+ FEATURE_PATTERN="^feature/v[0-9]+\.[0-9]+\.[0-9]+/[a-z0-9-]+$"
26
+ RELEASE_PATTERN="^release/v${BASE_VERSION}$"
27
+ HOTFIX_PATTERN="^hotfix/v[0-9]+\.[0-9]+\.[0-9]+(-patch\.[0-9]+)?$"
28
+
29
+ echo "Validating branch: $BRANCH_NAME"
30
+ echo "Current version: $BASE_VERSION"
31
+ echo "Latest published version: $LATEST_VERSION"
32
+
33
+ # For release branches, ensure version matches branch exactly
34
+ if [[ $BRANCH_NAME =~ ^release/v ]]; then
35
+ # Extract version from branch name
36
+ BRANCH_VERSION=$(echo $BRANCH_NAME | sed 's/release\/v//')
37
+
38
+ # Check if branch version matches package version
39
+ if [[ "$BRANCH_VERSION" != "$BASE_VERSION" ]]; then
40
+ echo "Error: Release branch version ($BRANCH_VERSION) must match package.json version ($BASE_VERSION)"
41
+ exit 1
42
+ fi
43
+
44
+ # Check if trying to publish a version behind latest
45
+ if [[ $(node -p "require('semver').lt('$BASE_VERSION', '$LATEST_VERSION')") == "true" ]]; then
46
+ echo "Error: Cannot publish version $BASE_VERSION as it is behind the latest published version $LATEST_VERSION"
47
+ exit 1
48
+ fi
49
+
50
+ echo "✅ Valid release branch name and version"
51
+ exit 0
52
+ fi
53
+
54
+ # Validate other branch types
55
+ if [[ $BRANCH_NAME =~ $FEATURE_PATTERN ]] || [[ $BRANCH_NAME =~ $HOTFIX_PATTERN ]]; then
56
+ echo "✅ Valid branch name"
57
+ exit 0
58
+ else
59
+ echo "Error: Invalid branch name format."
60
+ echo "Branch names must follow these formats:"
61
+ echo "- For feature branches: feature/v{version}/{feature-name}"
62
+ echo "- For release branches: release/v{version}"
63
+ echo "- For hotfix branches: hotfix/v{version}-patch.{number}"
64
+ exit 1
65
+ fi
@@ -0,0 +1,71 @@
1
+ const { execSync } = require("child_process");
2
+
3
+ const validatePublish = (type) => {
4
+ try {
5
+ // Get current branch
6
+ const currentBranch = execSync("git rev-parse --abbrev-ref HEAD")
7
+ .toString()
8
+ .trim();
9
+
10
+ // Get version from package.json
11
+ const { version } = require("../package.json");
12
+ const baseVersion = version.split("-")[0];
13
+
14
+ switch (type) {
15
+ case "production":
16
+ if (!currentBranch.startsWith("release/v")) {
17
+ throw new Error(
18
+ "Production publishing is only allowed from release branches.\n" +
19
+ "Current branch: " +
20
+ currentBranch +
21
+ "\n" +
22
+ "Expected format: release/v{version}"
23
+ );
24
+ }
25
+
26
+ // Verify release branch version matches package version
27
+ const releaseBranchVersion = currentBranch.split("/v")[1];
28
+ if (releaseBranchVersion !== baseVersion) {
29
+ throw new Error(
30
+ "Release branch version does not match package.json version.\n" +
31
+ `Branch version: ${releaseBranchVersion}\n` +
32
+ `Package version: ${baseVersion}\n` +
33
+ "Please update package.json version to match the release branch"
34
+ );
35
+ }
36
+ break;
37
+
38
+ case "hotfix":
39
+ if (!currentBranch.startsWith("hotfix/v")) {
40
+ throw new Error(
41
+ "Hotfix publishing is only allowed from hotfix branches.\n" +
42
+ "Current branch: " +
43
+ currentBranch +
44
+ "\n" +
45
+ "Expected format: hotfix/v{version}-patch.{number}"
46
+ );
47
+ }
48
+ break;
49
+
50
+ case "staging":
51
+ if (!currentBranch.startsWith("feature/v")) {
52
+ throw new Error(
53
+ "Staging publishing is only allowed from feature branches.\n" +
54
+ "Current branch: " +
55
+ currentBranch +
56
+ "\n" +
57
+ "Expected format: feature/v{version}/{feature-name}"
58
+ );
59
+ }
60
+ break;
61
+ }
62
+ } catch (error) {
63
+ console.error("\n❌ Publish validation failed:");
64
+ console.error(error.message);
65
+ process.exit(1);
66
+ }
67
+ };
68
+
69
+ // Get publish type from command line argument
70
+ const publishType = process.argv[2];
71
+ validatePublish(publishType);
@@ -1,9 +0,0 @@
1
- const fs = require("fs");
2
- const path = require("path");
3
- const package = require("../package.json");
4
-
5
- const content = `// This file is auto-generated. Do not modify it manually.
6
- export const SDK_VERSION = '${package.version}';
7
- `;
8
-
9
- fs.writeFileSync(path.join(__dirname, "../src/version.ts"), content);