xgem-cli 2.0.0-alpha.10 → 2.0.0-alpha.14

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 (43) hide show
  1. package/README.md +37 -63
  2. package/bin/xgem +46 -15
  3. package/bin/xgem.js +22 -20
  4. package/lib/bootstrap.sh +165 -0
  5. package/lib/ci.sh +44 -0
  6. package/lib/create.sh +140 -71
  7. package/lib/flutter.sh +10 -8
  8. package/lib/git.sh +291 -29
  9. package/lib/registry.sh +45 -0
  10. package/lib/release.sh +185 -0
  11. package/lib/scaffold.sh +21 -2
  12. package/lib/status.sh +42 -0
  13. package/lib/utils.sh +11 -0
  14. package/lib/version.sh +1 -1
  15. package/lib-win/bootstrap.js +189 -0
  16. package/lib-win/ci.js +43 -0
  17. package/lib-win/git.js +275 -21
  18. package/lib-win/registry.js +43 -0
  19. package/lib-win/release.js +143 -0
  20. package/lib-win/scaffold.js +64 -2
  21. package/lib-win/status.js +53 -0
  22. package/lib-win/utils.js +6 -1
  23. package/package.json +1 -1
  24. package/templates/node/build.sh.tmpl +9 -1
  25. package/templates/node/hard-clean.sh.tmpl +34 -2
  26. package/templates/node/lint.sh.tmpl +10 -0
  27. package/templates/node/start.sh.tmpl +9 -1
  28. package/templates/node/test.sh.tmpl +10 -0
  29. package/templates/webframework/build.sh.tmpl +10 -1
  30. package/templates/webframework/dev.sh.tmpl +9 -1
  31. package/templates/webframework/hard-clean.sh.tmpl +35 -2
  32. package/templates/webframework/lint.sh.tmpl +10 -0
  33. package/templates/webframework/test.sh.tmpl +10 -0
  34. package/templates-win/node/build.mjs.tmpl +8 -2
  35. package/templates-win/node/hard-clean.mjs.tmpl +24 -5
  36. package/templates-win/node/lint.mjs.tmpl +10 -0
  37. package/templates-win/node/start.mjs.tmpl +8 -1
  38. package/templates-win/node/test.mjs.tmpl +10 -0
  39. package/templates-win/webframework/build.mjs.tmpl +8 -1
  40. package/templates-win/webframework/dev.mjs.tmpl +8 -1
  41. package/templates-win/webframework/hard-clean.mjs.tmpl +28 -5
  42. package/templates-win/webframework/lint.mjs.tmpl +10 -0
  43. package/templates-win/webframework/test.mjs.tmpl +10 -0
@@ -0,0 +1,143 @@
1
+ // xgem Windows engine release command — port of lib/release.sh.
2
+
3
+ const fs = require('node:fs');
4
+ const { spawnSync } = require('node:child_process');
5
+ const { logInfo, logSuccess, logWarn, die } = require('./logger');
6
+ const { prompt, confirm } = require('./utils');
7
+ const { defaultRemote } = require('./git');
8
+
9
+ const VERSION_FILES = ['package.json', 'pubspec.yaml', 'Cargo.toml', 'pyproject.toml'];
10
+
11
+ function detectVersionFiles() {
12
+ return VERSION_FILES.filter((f) => fs.existsSync(f));
13
+ }
14
+
15
+ function readVersion(file) {
16
+ const content = fs.readFileSync(file, 'utf8');
17
+ if (file === 'package.json') {
18
+ const m = content.match(/"version"\s*:\s*"([^"]+)"/);
19
+ return m ? m[1] : '';
20
+ }
21
+ if (file === 'pubspec.yaml') {
22
+ const m = content.match(/^version:\s*(\S+)/m);
23
+ return m ? m[1] : '';
24
+ }
25
+ const m = content.match(/^version\s*=\s*"([^"]+)"/m);
26
+ return m ? m[1] : '';
27
+ }
28
+
29
+ function writeVersion(file, newVersion) {
30
+ const content = fs.readFileSync(file, 'utf8');
31
+ let updated;
32
+ if (file === 'package.json') {
33
+ updated = content.replace(/"version"\s*:\s*"[^"]+"/, `"version": "${newVersion}"`);
34
+ } else if (file === 'pubspec.yaml') {
35
+ updated = content.replace(/^version:.*/m, `version: ${newVersion}`);
36
+ } else {
37
+ updated = content.replace(/^version\s*=\s*"[^"]+"/m, `version = "${newVersion}"`);
38
+ }
39
+ fs.writeFileSync(file, updated);
40
+ }
41
+
42
+ function bumpVersion(version, kind) {
43
+ const plusIdx = version.indexOf('+');
44
+ const core = plusIdx === -1 ? version : version.slice(0, plusIdx);
45
+ const suffix = plusIdx === -1 ? '' : version.slice(plusIdx);
46
+
47
+ const m = core.match(/^(\d+)\.(\d+)\.(\d+)$/);
48
+ if (!m) return '';
49
+ let [major, minor, patch] = m.slice(1).map(Number);
50
+ if (kind === 'major') { major += 1; minor = 0; patch = 0; }
51
+ else if (kind === 'minor') { minor += 1; patch = 0; }
52
+ else { patch += 1; }
53
+ return `${major}.${minor}.${patch}${suffix}`;
54
+ }
55
+
56
+ function git(args, opts = {}) {
57
+ return spawnSync('git', args, { stdio: 'inherit', ...opts });
58
+ }
59
+
60
+ function gitCapture(args) {
61
+ const result = spawnSync('git', args, { encoding: 'utf8' });
62
+ return (result.stdout || '').trim();
63
+ }
64
+
65
+ function writeChangelog(version, lastTag) {
66
+ const range = lastTag ? `${lastTag}..HEAD` : 'HEAD';
67
+ const commits = gitCapture(['log', '--format=%s', range]).split(/\r?\n/).filter(Boolean);
68
+ const features = commits.filter((c) => c.startsWith('feat'));
69
+ const fixes = commits.filter((c) => c.startsWith('fix'));
70
+ const other = commits.filter((c) => !c.startsWith('feat') && !c.startsWith('fix'));
71
+
72
+ let section = `## v${version} - ${new Date().toISOString().slice(0, 10)}\n`;
73
+ if (features.length) section += `\n### Features\n${features.map((c) => `- ${c}`).join('\n')}\n`;
74
+ if (fixes.length) section += `\n### Fixes\n${fixes.map((c) => `- ${c}`).join('\n')}\n`;
75
+ if (other.length) section += `\n### Other\n${other.map((c) => `- ${c}`).join('\n')}\n`;
76
+
77
+ const existing = fs.existsSync('CHANGELOG.md')
78
+ ? fs.readFileSync('CHANGELOG.md', 'utf8').split('\n').slice(1).join('\n')
79
+ : '';
80
+ fs.writeFileSync('CHANGELOG.md', `# Changelog\n\n${section}\n${existing}`);
81
+ }
82
+
83
+ async function cmdRelease(bumpKind) {
84
+ const versionFiles = detectVersionFiles();
85
+ if (versionFiles.length === 0) {
86
+ die('No recognized version file (package.json/pubspec.yaml/Cargo.toml/pyproject.toml) found here.');
87
+ }
88
+
89
+ let file = versionFiles[0];
90
+ if (versionFiles.length > 1) {
91
+ console.log('Multiple version files found:');
92
+ versionFiles.forEach((f, i) => console.log(`${i + 1}) ${f}`));
93
+ const choice = await prompt(`Which is the canonical one to bump? [1-${versionFiles.length}]`);
94
+ const idx = parseInt(choice, 10) - 1;
95
+ if (Number.isNaN(idx) || idx < 0 || idx >= versionFiles.length) die('Invalid selection.');
96
+ file = versionFiles[idx];
97
+ }
98
+
99
+ const current = readVersion(file);
100
+ if (!current) die(`Could not read a version from ${file}.`);
101
+ logInfo(`Current version in ${file}: ${current}`);
102
+
103
+ if (bumpKind && !['major', 'minor', 'patch'].includes(bumpKind)) {
104
+ die(`Unknown bump kind '${bumpKind}'. Usage: xgem release [major|minor|patch]`);
105
+ }
106
+ if (!bumpKind) {
107
+ bumpKind = (await prompt('Bump [major/minor/patch]', 'patch')) || 'patch';
108
+ }
109
+
110
+ let newVersion = bumpVersion(current, bumpKind);
111
+ if (!newVersion) {
112
+ logWarn(`'${current}' isn't a plain X.Y.Z version — can't bump it automatically.`);
113
+ newVersion = await prompt('Enter the new version directly');
114
+ if (!newVersion) die('A new version is required.');
115
+ }
116
+
117
+ logInfo(`${current} -> ${newVersion}`);
118
+ if (!(await confirm('Write this version, update CHANGELOG.md, commit, and tag?'))) {
119
+ logInfo('Cancelled.');
120
+ return;
121
+ }
122
+
123
+ writeVersion(file, newVersion);
124
+
125
+ const lastTag = gitCapture(['describe', '--tags', '--abbrev=0']);
126
+ writeChangelog(newVersion, lastTag);
127
+
128
+ git(['add', file, 'CHANGELOG.md']);
129
+ if (git(['commit', '-m', `chore(release): v${newVersion}`]).status !== 0) die('Commit failed.');
130
+ if (git(['tag', '-a', `v${newVersion}`, '-m', `v${newVersion}`]).status !== 0) die('Tag failed.');
131
+ logSuccess(`Committed and tagged v${newVersion}.`);
132
+
133
+ const remote = defaultRemote();
134
+ if (remote && (await confirm(`Push commit and tag to '${remote}'?`))) {
135
+ const currentBranch = gitCapture(['branch', '--show-current']);
136
+ const pushed = git(['push', remote, currentBranch]).status === 0
137
+ && git(['push', remote, `v${newVersion}`]).status === 0;
138
+ if (pushed) logSuccess(`Pushed v${newVersion} to '${remote}'.`);
139
+ else die('Push failed — the commit and tag are safe locally.');
140
+ }
141
+ }
142
+
143
+ module.exports = { cmdRelease };
@@ -6,6 +6,7 @@ const fs = require('node:fs');
6
6
  const path = require('node:path');
7
7
  const { spawnSync } = require('node:child_process');
8
8
  const { logInfo, logSuccess, logWarn, logError, die } = require('./logger');
9
+ const { prompt } = require('./utils');
9
10
 
10
11
  const ALL_FRAMEWORKS = ['flutter', 'node', 'python', 'react', 'vue', 'angular', 'next', 'go', 'rust', 'docker'];
11
12
 
@@ -22,8 +23,30 @@ const FRAMEWORK_SCRIPTS = {
22
23
  docker: ['hard-clean', 'build-up'],
23
24
  };
24
25
 
26
+ const WEB_FRAMEWORKS = ['node', 'react', 'vue', 'angular', 'next'];
27
+
28
+ // Only offer lint/test automation when the current directory's
29
+ // package.json actually declares those scripts, instead of always
30
+ // generating scripts that fail with "Missing script" on projects that
31
+ // don't have them.
32
+ function packageJsonHasScript(scriptName) {
33
+ try {
34
+ const pkg = JSON.parse(require('node:fs').readFileSync('package.json', 'utf8'));
35
+ return Boolean(pkg.scripts && pkg.scripts[scriptName]);
36
+ } catch {
37
+ return false;
38
+ }
39
+ }
40
+
25
41
  function frameworkScripts(fw) {
26
- return FRAMEWORK_SCRIPTS[fw] || null;
42
+ const base = FRAMEWORK_SCRIPTS[fw];
43
+ if (!base) return null;
44
+ if (!WEB_FRAMEWORKS.includes(fw)) return base;
45
+
46
+ const scripts = [...base];
47
+ if (packageJsonHasScript('lint')) scripts.push('lint');
48
+ if (packageJsonHasScript('test')) scripts.push('test');
49
+ return scripts;
27
50
  }
28
51
 
29
52
  function templateDir(fw) {
@@ -77,4 +100,43 @@ function getRemainingFrameworks(configDir) {
77
100
  return ALL_FRAMEWORKS.filter((fw) => !fs.existsSync(path.join(configDir, fw)));
78
101
  }
79
102
 
80
- module.exports = { ALL_FRAMEWORKS, frameworkScripts, injectTemplates, runScript, getRemainingFrameworks };
103
+ // Some teams want the generated scripts checked in so collaborators get the
104
+ // same automation; others want them private/local-only. Ask instead of
105
+ // always gitignoring.
106
+ async function updateGitignore(configDir) {
107
+ const gitignorePath = '.gitignore';
108
+ const ignoreChoice = (await prompt(`Should ${configDir}/ be ignored by git (private to you), or tracked so collaborators get the same scripts? [ignore/track]`, 'ignore')).toLowerCase();
109
+
110
+ if (ignoreChoice.startsWith('t')) {
111
+ if (fs.existsSync(gitignorePath)) {
112
+ const lines = fs.readFileSync(gitignorePath, 'utf8').split(/\r?\n/).filter((l) => l !== `${configDir}/`);
113
+ fs.writeFileSync(gitignorePath, lines.join('\n'));
114
+ logInfo(`Removed existing ${configDir}/ entry from .gitignore since you chose to track it.`);
115
+ }
116
+ logSuccess(`${configDir}/ will be tracked in git.`);
117
+ return;
118
+ }
119
+
120
+ if (fs.existsSync(gitignorePath)) {
121
+ const content = fs.readFileSync(gitignorePath, 'utf8');
122
+ if (!content.includes(`${configDir}/`)) {
123
+ fs.appendFileSync(gitignorePath, `\n${configDir}/\n`);
124
+ logSuccess('Added automation tracking to .gitignore');
125
+ }
126
+ } else {
127
+ fs.writeFileSync(gitignorePath, `${configDir}/\n`);
128
+ logSuccess('Created .gitignore and hidden tracking layer folder references.');
129
+ }
130
+ }
131
+
132
+ // bookkeeping(fw, configDir) — creates configDir/fw's scripts and updates
133
+ // .gitignore. Called from both `xgem init` and `xgem bootstrap` so a
134
+ // project scaffolded either way ends up in the same state.
135
+ async function bookkeeping(fw, configDir) {
136
+ fs.mkdirSync(configDir, { recursive: true });
137
+ injectTemplates(fw, configDir);
138
+ logSuccess(`Successfully appended standard scripts for: ${configDir}/${fw}`);
139
+ await updateGitignore(configDir);
140
+ }
141
+
142
+ module.exports = { ALL_FRAMEWORKS, frameworkScripts, injectTemplates, runScript, getRemainingFrameworks, updateGitignore, bookkeeping };
@@ -0,0 +1,53 @@
1
+ // xgem status — port of lib/status.sh: a one-screen dashboard across every
2
+ // project xgem has ever scaffolded.
3
+
4
+ const fs = require('node:fs');
5
+ const path = require('node:path');
6
+ const { spawnSync } = require('node:child_process');
7
+ const { logInfo } = require('./logger');
8
+ const { registryList } = require('./registry');
9
+
10
+ function gitCaptureIn(cwd, args) {
11
+ const result = spawnSync('git', args, { cwd, encoding: 'utf8' });
12
+ return result.status === 0 ? (result.stdout || '').trim() : '';
13
+ }
14
+
15
+ function statusRow(projectPath, configDir) {
16
+ const branch = gitCaptureIn(projectPath, ['branch', '--show-current']) || '-';
17
+ const porcelain = gitCaptureIn(projectPath, ['status', '--porcelain']);
18
+ const dirty = porcelain ? porcelain.split(/\r?\n/).filter(Boolean).length : 0;
19
+
20
+ const counts = gitCaptureIn(projectPath, ['rev-list', '--left-right', '--count', '@{u}...HEAD']);
21
+ let aheadBehind = '-';
22
+ if (counts) {
23
+ const [behind, ahead] = counts.split(/\s+/);
24
+ aheadBehind = `-${behind} +${ahead}`;
25
+ }
26
+
27
+ const configPath = path.join(projectPath, configDir);
28
+ const frameworks = fs.existsSync(configPath)
29
+ ? fs.readdirSync(configPath, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name).join(',') || '-'
30
+ : '-';
31
+
32
+ return { projectPath, branch, dirty, aheadBehind, frameworks };
33
+ }
34
+
35
+ function pad(str, width) {
36
+ return str.length >= width ? str : str + ' '.repeat(width - str.length);
37
+ }
38
+
39
+ async function cmdStatus(configDir) {
40
+ const projects = registryList(configDir);
41
+ if (projects.length === 0) {
42
+ logInfo("No xgem-tracked projects found yet — run 'xgem init' in a project to start tracking it.");
43
+ return;
44
+ }
45
+
46
+ console.log(`${pad('PROJECT', 45)} ${pad('BRANCH', 20)} ${pad('DIRTY', 6)} ${pad('AHEAD/BEHIND', 12)} FRAMEWORKS`);
47
+ for (const p of projects) {
48
+ const row = statusRow(p, configDir);
49
+ console.log(`${pad(row.projectPath, 45)} ${pad(row.branch, 20)} ${pad(String(row.dirty), 6)} ${pad(row.aheadBehind, 12)} ${row.frameworks}`);
50
+ }
51
+ }
52
+
53
+ module.exports = { cmdStatus };
package/lib-win/utils.js CHANGED
@@ -32,6 +32,11 @@ function getVersion(name, args = ['--version']) {
32
32
  return out ? out.trim() : null;
33
33
  }
34
34
 
35
+ // openUrl(url) — best-effort browser open on Windows.
36
+ function openUrl(url) {
37
+ spawnSync('cmd', ['/c', 'start', '""', url], { stdio: 'ignore' });
38
+ }
39
+
35
40
  // confirm(prompt) -> Promise<boolean>. Honors XGEM_YES (auto-approve) and
36
41
  // XGEM_DRY_RUN (always decline, just like lib/utils.sh's confirm()).
37
42
  function confirm(prompt) {
@@ -64,4 +69,4 @@ function prompt(question, defaultValue = '') {
64
69
  });
65
70
  }
66
71
 
67
- module.exports = { detectArch, hasCmd, requireCmd, getVersion, confirm, prompt };
72
+ module.exports = { detectArch, hasCmd, requireCmd, getVersion, confirm, prompt, openUrl };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xgem-cli",
3
- "version": "2.0.0-alpha.10",
3
+ "version": "2.0.0-alpha.14",
4
4
  "description": "Framework-aware automation CLI: scaffolds and runs clean/build/dev scripts per project type, an environment doctor, a SwiftPM-aware iOS build engine, and a git workflow helper. Runs natively on macOS, Linux, and Windows.",
5
5
  "bin": {
6
6
  "xgem": "bin/xgem.js"
@@ -1,3 +1,11 @@
1
1
  #!/bin/bash
2
2
  echo -e "\033[1;34mBuilding Node project...\033[0m"
3
- npm run build
3
+ if [ -f pnpm-lock.yaml ]; then
4
+ pnpm run build
5
+ elif [ -f yarn.lock ]; then
6
+ yarn build
7
+ elif [ -f bun.lockb ] || [ -f bun.lock ]; then
8
+ bun run build
9
+ else
10
+ npm run build
11
+ fi
@@ -1,3 +1,35 @@
1
1
  #!/bin/bash
2
- echo -e "\033[1;33mNuking node_modules and resetting package locks...\033[0m"
3
- rm -rf node_modules package-lock.json yarn.lock && npm install
2
+ # Detect the package manager actually in use instead of assuming npm —
3
+ # blindly deleting yarn.lock/pnpm-lock.yaml on a project that uses them
4
+ # and running `npm install` instead corrupts the project by introducing a
5
+ # conflicting lockfile.
6
+ PKG_MANAGER=npm
7
+ if [ -f pnpm-lock.yaml ]; then
8
+ PKG_MANAGER=pnpm
9
+ elif [ -f yarn.lock ]; then
10
+ PKG_MANAGER=yarn
11
+ elif [ -f bun.lockb ] || [ -f bun.lock ]; then
12
+ PKG_MANAGER=bun
13
+ elif [ -f package.json ]; then
14
+ declared=$(grep -o '"packageManager"[[:space:]]*:[[:space:]]*"[a-z]*' package.json 2>/dev/null | grep -oE '[a-z]+$')
15
+ case "$declared" in npm|yarn|pnpm|bun) PKG_MANAGER=$declared ;; esac
16
+ fi
17
+
18
+ if [ -f .nvmrc ] && command -v node >/dev/null 2>&1; then
19
+ wanted=$(tr -d 'v[:space:]' < .nvmrc)
20
+ have=$(node -v | tr -d 'v')
21
+ case "$have" in
22
+ "$wanted"*) ;;
23
+ *) echo -e "\033[33mWarning: .nvmrc wants Node $wanted, but 'node -v' reports $have.\033[0m" ;;
24
+ esac
25
+ fi
26
+
27
+ echo -e "\033[1;33mNuking node_modules and resetting package locks (package manager: $PKG_MANAGER)...\033[0m"
28
+ rm -rf node_modules
29
+
30
+ case "$PKG_MANAGER" in
31
+ yarn) rm -f yarn.lock; yarn install ;;
32
+ pnpm) rm -f pnpm-lock.yaml; pnpm install ;;
33
+ bun) rm -f bun.lockb bun.lock; bun install ;;
34
+ *) rm -f package-lock.json; npm install ;;
35
+ esac
@@ -0,0 +1,10 @@
1
+ #!/bin/bash
2
+ if [ -f pnpm-lock.yaml ]; then
3
+ pnpm run lint
4
+ elif [ -f yarn.lock ]; then
5
+ yarn lint
6
+ elif [ -f bun.lockb ] || [ -f bun.lock ]; then
7
+ bun run lint
8
+ else
9
+ npm run lint
10
+ fi
@@ -1,2 +1,10 @@
1
1
  #!/bin/bash
2
- npm run start
2
+ if [ -f pnpm-lock.yaml ]; then
3
+ pnpm run start
4
+ elif [ -f yarn.lock ]; then
5
+ yarn start
6
+ elif [ -f bun.lockb ] || [ -f bun.lock ]; then
7
+ bun run start
8
+ else
9
+ npm run start
10
+ fi
@@ -0,0 +1,10 @@
1
+ #!/bin/bash
2
+ if [ -f pnpm-lock.yaml ]; then
3
+ pnpm run test
4
+ elif [ -f yarn.lock ]; then
5
+ yarn test
6
+ elif [ -f bun.lockb ] || [ -f bun.lock ]; then
7
+ bun run test
8
+ else
9
+ npm run test
10
+ fi
@@ -1,2 +1,11 @@
1
1
  #!/bin/bash
2
- npm run build
2
+ # Use whatever package manager this project actually uses.
3
+ if [ -f pnpm-lock.yaml ]; then
4
+ pnpm run build
5
+ elif [ -f yarn.lock ]; then
6
+ yarn build
7
+ elif [ -f bun.lockb ] || [ -f bun.lock ]; then
8
+ bun run build
9
+ else
10
+ npm run build
11
+ fi
@@ -1,2 +1,10 @@
1
1
  #!/bin/bash
2
- npm run dev
2
+ if [ -f pnpm-lock.yaml ]; then
3
+ pnpm run dev
4
+ elif [ -f yarn.lock ]; then
5
+ yarn dev
6
+ elif [ -f bun.lockb ] || [ -f bun.lock ]; then
7
+ bun run dev
8
+ else
9
+ npm run dev
10
+ fi
@@ -1,3 +1,36 @@
1
1
  #!/bin/bash
2
- echo -e "\033[1;33mResetting __FRAMEWORK__ dependencies...\033[0m"
3
- rm -rf node_modules package-lock.json yarn.lock && npm install
2
+ # Detect the package manager actually in use instead of assuming npm —
3
+ # blindly deleting yarn.lock/pnpm-lock.yaml on a project that uses them
4
+ # and running `npm install` instead corrupts the project by introducing a
5
+ # conflicting lockfile.
6
+ PKG_MANAGER=npm
7
+ if [ -f pnpm-lock.yaml ]; then
8
+ PKG_MANAGER=pnpm
9
+ elif [ -f yarn.lock ]; then
10
+ PKG_MANAGER=yarn
11
+ elif [ -f bun.lockb ] || [ -f bun.lock ]; then
12
+ PKG_MANAGER=bun
13
+ elif [ -f package.json ]; then
14
+ declared=$(grep -o '"packageManager"[[:space:]]*:[[:space:]]*"[a-z]*' package.json 2>/dev/null | grep -oE '[a-z]+$')
15
+ case "$declared" in npm|yarn|pnpm|bun) PKG_MANAGER=$declared ;; esac
16
+ fi
17
+
18
+ # Warn (don't block) on a Node version mismatch against .nvmrc, if present.
19
+ if [ -f .nvmrc ] && command -v node >/dev/null 2>&1; then
20
+ wanted=$(tr -d 'v[:space:]' < .nvmrc)
21
+ have=$(node -v | tr -d 'v')
22
+ case "$have" in
23
+ "$wanted"*) ;;
24
+ *) echo -e "\033[33mWarning: .nvmrc wants Node $wanted, but 'node -v' reports $have.\033[0m" ;;
25
+ esac
26
+ fi
27
+
28
+ echo -e "\033[1;33mResetting __FRAMEWORK__ dependencies (package manager: $PKG_MANAGER)...\033[0m"
29
+ rm -rf node_modules
30
+
31
+ case "$PKG_MANAGER" in
32
+ yarn) rm -f yarn.lock; yarn install ;;
33
+ pnpm) rm -f pnpm-lock.yaml; pnpm install ;;
34
+ bun) rm -f bun.lockb bun.lock; bun install ;;
35
+ *) rm -f package-lock.json; npm install ;;
36
+ esac
@@ -0,0 +1,10 @@
1
+ #!/bin/bash
2
+ if [ -f pnpm-lock.yaml ]; then
3
+ pnpm run lint
4
+ elif [ -f yarn.lock ]; then
5
+ yarn lint
6
+ elif [ -f bun.lockb ] || [ -f bun.lock ]; then
7
+ bun run lint
8
+ else
9
+ npm run lint
10
+ fi
@@ -0,0 +1,10 @@
1
+ #!/bin/bash
2
+ if [ -f pnpm-lock.yaml ]; then
3
+ pnpm run test
4
+ elif [ -f yarn.lock ]; then
5
+ yarn test
6
+ elif [ -f bun.lockb ] || [ -f bun.lock ]; then
7
+ bun run test
8
+ else
9
+ npm run test
10
+ fi
@@ -1,4 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  import { execSync } from 'node:child_process';
3
- console.log('Building Node project...');
4
- execSync('npm run build', { stdio: 'inherit' });
3
+ import { existsSync } from 'node:fs';
4
+
5
+ let cmd = 'npm run build';
6
+ if (existsSync('pnpm-lock.yaml')) cmd = 'pnpm run build';
7
+ else if (existsSync('yarn.lock')) cmd = 'yarn build';
8
+ else if (existsSync('bun.lockb') || existsSync('bun.lock')) cmd = 'bun run build';
9
+
10
+ execSync(cmd, { stdio: 'inherit' });
@@ -1,9 +1,28 @@
1
1
  #!/usr/bin/env node
2
2
  import { execSync } from 'node:child_process';
3
- import { rmSync } from 'node:fs';
3
+ import { existsSync, rmSync, readFileSync } from 'node:fs';
4
4
 
5
- console.log('Nuking node_modules and resetting package locks...');
5
+ function detectPkgManager() {
6
+ if (existsSync('pnpm-lock.yaml')) return 'pnpm';
7
+ if (existsSync('yarn.lock')) return 'yarn';
8
+ if (existsSync('bun.lockb') || existsSync('bun.lock')) return 'bun';
9
+ if (existsSync('package.json')) {
10
+ try {
11
+ const pkg = JSON.parse(readFileSync('package.json', 'utf8'));
12
+ if (['npm', 'yarn', 'pnpm', 'bun'].includes(pkg.packageManager?.split('@')[0])) {
13
+ return pkg.packageManager.split('@')[0];
14
+ }
15
+ } catch { /* ignore malformed package.json */ }
16
+ }
17
+ return 'npm';
18
+ }
19
+
20
+ const pm = detectPkgManager();
21
+ console.log(`Nuking node_modules and resetting package locks (package manager: ${pm})...`);
6
22
  rmSync('node_modules', { recursive: true, force: true });
7
- rmSync('package-lock.json', { force: true });
8
- rmSync('yarn.lock', { force: true });
9
- execSync('npm install', { stdio: 'inherit' });
23
+
24
+ const lockfiles = { npm: ['package-lock.json'], yarn: ['yarn.lock'], pnpm: ['pnpm-lock.yaml'], bun: ['bun.lockb', 'bun.lock'] };
25
+ for (const f of lockfiles[pm]) rmSync(f, { force: true });
26
+
27
+ const installCmd = { npm: 'npm install', yarn: 'yarn', pnpm: 'pnpm install', bun: 'bun install' };
28
+ execSync(installCmd[pm], { stdio: 'inherit' });
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env node
2
+ import { execSync } from 'node:child_process';
3
+ import { existsSync } from 'node:fs';
4
+
5
+ let cmd = 'npm run lint';
6
+ if (existsSync('pnpm-lock.yaml')) cmd = 'pnpm run lint';
7
+ else if (existsSync('yarn.lock')) cmd = 'yarn lint';
8
+ else if (existsSync('bun.lockb') || existsSync('bun.lock')) cmd = 'bun run lint';
9
+
10
+ execSync(cmd, { stdio: 'inherit' });
@@ -1,3 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  import { execSync } from 'node:child_process';
3
- execSync('npm run start', { stdio: 'inherit' });
3
+ import { existsSync } from 'node:fs';
4
+
5
+ let cmd = 'npm run start';
6
+ if (existsSync('pnpm-lock.yaml')) cmd = 'pnpm run start';
7
+ else if (existsSync('yarn.lock')) cmd = 'yarn start';
8
+ else if (existsSync('bun.lockb') || existsSync('bun.lock')) cmd = 'bun run start';
9
+
10
+ execSync(cmd, { stdio: 'inherit' });
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env node
2
+ import { execSync } from 'node:child_process';
3
+ import { existsSync } from 'node:fs';
4
+
5
+ let cmd = 'npm run test';
6
+ if (existsSync('pnpm-lock.yaml')) cmd = 'pnpm run test';
7
+ else if (existsSync('yarn.lock')) cmd = 'yarn test';
8
+ else if (existsSync('bun.lockb') || existsSync('bun.lock')) cmd = 'bun run test';
9
+
10
+ execSync(cmd, { stdio: 'inherit' });
@@ -1,3 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  import { execSync } from 'node:child_process';
3
- execSync('npm run build', { stdio: 'inherit' });
3
+ import { existsSync } from 'node:fs';
4
+
5
+ let cmd = 'npm run build';
6
+ if (existsSync('pnpm-lock.yaml')) cmd = 'pnpm run build';
7
+ else if (existsSync('yarn.lock')) cmd = 'yarn build';
8
+ else if (existsSync('bun.lockb') || existsSync('bun.lock')) cmd = 'bun run build';
9
+
10
+ execSync(cmd, { stdio: 'inherit' });
@@ -1,3 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  import { execSync } from 'node:child_process';
3
- execSync('npm run dev', { stdio: 'inherit' });
3
+ import { existsSync } from 'node:fs';
4
+
5
+ let cmd = 'npm run dev';
6
+ if (existsSync('pnpm-lock.yaml')) cmd = 'pnpm run dev';
7
+ else if (existsSync('yarn.lock')) cmd = 'yarn dev';
8
+ else if (existsSync('bun.lockb') || existsSync('bun.lock')) cmd = 'bun run dev';
9
+
10
+ execSync(cmd, { stdio: 'inherit' });
@@ -1,9 +1,32 @@
1
1
  #!/usr/bin/env node
2
2
  import { execSync } from 'node:child_process';
3
- import { rmSync } from 'node:fs';
3
+ import { existsSync, rmSync, readFileSync } from 'node:fs';
4
4
 
5
- console.log('Resetting __FRAMEWORK__ dependencies...');
5
+ // Detect the package manager actually in use instead of assuming npm —
6
+ // blindly deleting yarn.lock/pnpm-lock.yaml on a project that uses them
7
+ // and running `npm install` instead corrupts the project by introducing a
8
+ // conflicting lockfile.
9
+ function detectPkgManager() {
10
+ if (existsSync('pnpm-lock.yaml')) return 'pnpm';
11
+ if (existsSync('yarn.lock')) return 'yarn';
12
+ if (existsSync('bun.lockb') || existsSync('bun.lock')) return 'bun';
13
+ if (existsSync('package.json')) {
14
+ try {
15
+ const pkg = JSON.parse(readFileSync('package.json', 'utf8'));
16
+ if (['npm', 'yarn', 'pnpm', 'bun'].includes(pkg.packageManager?.split('@')[0])) {
17
+ return pkg.packageManager.split('@')[0];
18
+ }
19
+ } catch { /* ignore malformed package.json */ }
20
+ }
21
+ return 'npm';
22
+ }
23
+
24
+ const pm = detectPkgManager();
25
+ console.log(`Resetting __FRAMEWORK__ dependencies (package manager: ${pm})...`);
6
26
  rmSync('node_modules', { recursive: true, force: true });
7
- rmSync('package-lock.json', { force: true });
8
- rmSync('yarn.lock', { force: true });
9
- execSync('npm install', { stdio: 'inherit' });
27
+
28
+ const lockfiles = { npm: ['package-lock.json'], yarn: ['yarn.lock'], pnpm: ['pnpm-lock.yaml'], bun: ['bun.lockb', 'bun.lock'] };
29
+ for (const f of lockfiles[pm]) rmSync(f, { force: true });
30
+
31
+ const installCmd = { npm: 'npm install', yarn: 'yarn', pnpm: 'pnpm install', bun: 'bun install' };
32
+ execSync(installCmd[pm], { stdio: 'inherit' });
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env node
2
+ import { execSync } from 'node:child_process';
3
+ import { existsSync } from 'node:fs';
4
+
5
+ let cmd = 'npm run lint';
6
+ if (existsSync('pnpm-lock.yaml')) cmd = 'pnpm run lint';
7
+ else if (existsSync('yarn.lock')) cmd = 'yarn lint';
8
+ else if (existsSync('bun.lockb') || existsSync('bun.lock')) cmd = 'bun run lint';
9
+
10
+ execSync(cmd, { stdio: 'inherit' });