create-epinoetics-app 1.0.6 → 1.0.7

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.
@@ -6,6 +6,7 @@ import { resolve, dirname } from 'path';
6
6
  import { fileURLToPath } from 'url';
7
7
  import { run } from '../src/index.js';
8
8
  import { runAdd } from '../src/adder.js';
9
+ import { runSync } from '../src/syncer.js';
9
10
 
10
11
  const __dirname = dirname(fileURLToPath(import.meta.url));
11
12
  const pkg = JSON.parse(readFileSync(resolve(__dirname, '../package.json'), 'utf8'));
@@ -37,8 +38,12 @@ program
37
38
  program
38
39
  .command('add')
39
40
  .description('Add a feature to an existing project (run from code/api-nest)')
40
- .action(() => {
41
- runAdd();
42
- });
41
+ .action(() => runAdd());
42
+
43
+ // ── sync ──────────────────────────────────────────────────────────
44
+ program
45
+ .command('sync')
46
+ .description('Pull latest boilerplate updates (run from code/api-nest)')
47
+ .action(() => runSync());
43
48
 
44
49
  program.parse(process.argv);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-epinoetics-app",
3
- "version": "1.0.6",
3
+ "version": "1.0.7",
4
4
  "description": "Scaffold a new project from your boilerplate repos",
5
5
  "type": "module",
6
6
  "bin": {
package/src/cloner.js CHANGED
@@ -80,8 +80,26 @@ export async function cloneAndMergeNest({ projectName, database, selectedFeature
80
80
  // 6. Write scaffold.config.json
81
81
  writeScaffoldConfig({ dest, database, selectedFeatures, repoUrl });
82
82
 
83
- // 7. Strip .git
84
- fs.rmSync(join(dest, '.git'), { recursive: true, force: true });
83
+ // 7. Protect src/features/ from future syncs
84
+ writeGitExclude({ dest });
85
+
86
+ // NOTE: .git is intentionally kept so `sync` can pull updates later
87
+ }
88
+
89
+ // ── Write .git/info/exclude to protect key dirs from sync ─────────
90
+ function writeGitExclude({ dest }) {
91
+ const excludePath = join(dest, '.git', 'info', 'exclude');
92
+ const lines = [
93
+ '# Added by create-epinoetics-app — do not edit',
94
+ 'src/features/',
95
+ 'scaffold.config.json',
96
+ '.env',
97
+ '.env.local',
98
+ ].join('\n');
99
+
100
+ fs.mkdirSync(join(dest, '.git', 'info'), { recursive: true });
101
+ fs.writeFileSync(excludePath, lines, 'utf8');
102
+ p.log.step(`Protected src/features/ from sync`);
85
103
  }
86
104
 
87
105
  // ── Write scaffold.config.json ────────────────────────────────────
package/src/syncer.js ADDED
@@ -0,0 +1,99 @@
1
+ import * as p from '@clack/prompts';
2
+ import chalk from 'chalk';
3
+ import { join } from 'path';
4
+ import { simpleGit } from 'simple-git';
5
+ import { NEST_DATABASES } from './repos.js';
6
+ import fs from 'fs';
7
+
8
+ export async function runSync() {
9
+ console.log('');
10
+ p.intro(
11
+ chalk.bgHex('#7c3aed').white(' create-epinoetics-app sync ') +
12
+ chalk.dim(' pull latest boilerplate updates')
13
+ );
14
+
15
+ const cwd = process.cwd();
16
+
17
+ // ── 1. Read scaffold.config.json ──────────────────────────────────
18
+ const configPath = join(cwd, 'scaffold.config.json');
19
+ if (!fs.existsSync(configPath)) {
20
+ p.log.error(
21
+ 'No scaffold.config.json found.\n' +
22
+ chalk.dim('Make sure you are inside the code/api-nest directory.')
23
+ );
24
+ process.exit(1);
25
+ }
26
+
27
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
28
+ const database = NEST_DATABASES.find(d => d.id === config.database);
29
+
30
+ p.note(
31
+ [
32
+ `${chalk.dim('branch '.padEnd(14))} ${chalk.cyan(config.branch)}`,
33
+ `${chalk.dim('features'.padEnd(14))} ${config.features.length ? config.features.map(f => chalk.cyan(f)).join(chalk.dim(', ')) : chalk.dim('none')}`,
34
+ ].join('\n'),
35
+ 'Syncing'
36
+ );
37
+
38
+ const confirm = await p.confirm({
39
+ message: `Pull latest changes from ${chalk.cyan(config.branch)}?`,
40
+ });
41
+ if (p.isCancel(confirm) || !confirm) return cancel();
42
+
43
+ // ── 2. Make sure .git exists ──────────────────────────────────────
44
+ const gitDir = join(cwd, '.git');
45
+ if (!fs.existsSync(gitDir)) {
46
+ p.log.error(
47
+ '.git directory not found.\n' +
48
+ chalk.dim('Re-scaffold this project to enable sync.')
49
+ );
50
+ process.exit(1);
51
+ }
52
+
53
+ // ── 3. Stash any local changes ────────────────────────────────────
54
+ const git = simpleGit(cwd);
55
+ const spinner = p.spinner();
56
+
57
+ spinner.start('Stashing local changes...');
58
+ try {
59
+ await git.stash(['push', '--include-untracked', '-m', 'create-epinoetics-app sync']);
60
+ spinner.stop(`${chalk.green('✓')} Local changes stashed`);
61
+ } catch {
62
+ spinner.stop(chalk.dim('Nothing to stash'));
63
+ }
64
+
65
+ // ── 4. Pull latest from the db branch ────────────────────────────
66
+ const pullSpinner = p.spinner();
67
+ pullSpinner.start(`Pulling latest from ${chalk.cyan(config.branch)}...`);
68
+
69
+ try {
70
+ await git.pull('origin', config.branch, ['--rebase']);
71
+ pullSpinner.stop(`${chalk.green('✓')} Pulled latest changes`);
72
+ } catch (err) {
73
+ pullSpinner.stop(`${chalk.red('✗')} Pull failed ${chalk.dim(err.message)}`);
74
+ p.log.warn('Restoring your stashed changes...');
75
+ await git.stash(['pop']).catch(() => {});
76
+ process.exit(1);
77
+ }
78
+
79
+ // ── 5. Restore stashed changes ────────────────────────────────────
80
+ const stashList = await git.stashList();
81
+ if (stashList.total > 0) {
82
+ const restoreSpinner = p.spinner();
83
+ restoreSpinner.start('Restoring your local changes...');
84
+ try {
85
+ await git.stash(['pop']);
86
+ restoreSpinner.stop(`${chalk.green('✓')} Local changes restored`);
87
+ } catch (err) {
88
+ restoreSpinner.stop(`${chalk.yellow('⚠')} Conflict restoring stash — resolve manually`);
89
+ p.log.warn(`Run ${chalk.cyan('git stash pop')} to restore your changes.`);
90
+ }
91
+ }
92
+
93
+ p.outro(chalk.green('Sync complete!'));
94
+ }
95
+
96
+ function cancel() {
97
+ p.cancel('Cancelled.');
98
+ process.exit(0);
99
+ }