@jsonpages/cli 2.0.0 → 3.0.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,13 +1,12 @@
1
1
  {
2
2
  "name": "@jsonpages/cli",
3
- "version": "2.0.0",
3
+ "version": "3.0.0",
4
4
  "description": "JsonPages CLI - Sovereign Projection Engine",
5
5
  "type": "module",
6
- "bin": {
7
- "jsonpages": "./src/index.js"
8
- },
6
+ "bin": { "jsonpages": "./src/index.js" },
9
7
  "scripts": {
10
- "build": "tsc -p ."
8
+ "build": "tsc -p .",
9
+ "build:manifest": "node scripts/build-projection-manifest.mjs"
11
10
  },
12
11
  "dependencies": {
13
12
  "chalk": "^5.3.0",
@@ -21,4 +20,4 @@
21
20
  "@types/node": "^22.13.1",
22
21
  "typescript": "^5.7.3"
23
22
  }
24
- }
23
+ }
@@ -0,0 +1,59 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Build-time script: parses src_tenant_alpha.sh (heredoc format) and emits
4
+ * projection-manifest.json for cross-platform CLI projection (Node-only, no shell).
5
+ * Run from repo root: node packages/cli/scripts/build-projection-manifest.mjs
6
+ */
7
+ import fs from 'fs';
8
+ import path from 'path';
9
+ import { fileURLToPath } from 'url';
10
+
11
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
12
+ const ASSETS_DIR = path.resolve(__dirname, '../assets');
13
+ const SH_PATH = path.join(ASSETS_DIR, 'src_tenant_alpha.sh');
14
+ const MANIFEST_PATH = path.join(ASSETS_DIR, 'projection-manifest.json');
15
+
16
+ const HEREDOC_MARKER = 'END_OF_FILE_CONTENT';
17
+ const PATTERN = /cat << 'END_OF_FILE_CONTENT' > "([^"]+)"\n/g;
18
+
19
+ function parseShToManifest(shContent) {
20
+ const manifest = Object.create(null);
21
+ let match;
22
+ while ((match = PATTERN.exec(shContent)) !== null) {
23
+ const filePath = match[1];
24
+ const contentStart = match.index + match[0].length;
25
+ const contentEnd = findHeredocEnd(shContent, contentStart);
26
+ const content = contentEnd === -1
27
+ ? shContent.slice(contentStart)
28
+ : shContent.slice(contentStart, contentEnd);
29
+ manifest[filePath] = content;
30
+ }
31
+ return manifest;
32
+ }
33
+
34
+ function findHeredocEnd(text, fromIndex) {
35
+ let i = fromIndex;
36
+ while (i < text.length) {
37
+ const lineEnd = text.indexOf('\n', i);
38
+ const line = lineEnd === -1 ? text.slice(i) : text.slice(i, lineEnd);
39
+ if (line.trim() === HEREDOC_MARKER) {
40
+ return i;
41
+ }
42
+ i = lineEnd === -1 ? text.length : lineEnd + 1;
43
+ }
44
+ return -1;
45
+ }
46
+
47
+ function main() {
48
+ if (!fs.existsSync(SH_PATH)) {
49
+ console.error('Missing asset:', SH_PATH);
50
+ process.exit(1);
51
+ }
52
+ const shContent = fs.readFileSync(SH_PATH, 'utf8');
53
+ const manifest = parseShToManifest(shContent);
54
+ const count = Object.keys(manifest).length;
55
+ fs.writeFileSync(MANIFEST_PATH, JSON.stringify(manifest, null, 0), 'utf8');
56
+ console.log(`Wrote ${MANIFEST_PATH} (${count} files).`);
57
+ }
58
+
59
+ main();
package/src/index.js CHANGED
@@ -6,11 +6,18 @@ import path from 'path';
6
6
  import { execa } from 'execa';
7
7
  import ora from 'ora';
8
8
  import { fileURLToPath } from 'url';
9
+ import { projectSrc } from './projection.js';
9
10
 
10
11
  // 🛡️ Risoluzione path per rendere la CLI shippable
11
12
  const __filename = fileURLToPath(import.meta.url);
12
13
  const __dirname = path.dirname(__filename);
13
14
 
15
+ /** Cross-platform: on Windows, npm/yalc are .cmd; execa needs shell to resolve them. */
16
+ const execOpts = (cwd) => ({
17
+ cwd,
18
+ ...(process.platform === 'win32' && { shell: true }),
19
+ });
20
+
14
21
  const program = new Command();
15
22
 
16
23
  program
@@ -36,7 +43,8 @@ program
36
43
  const defaultScriptPath = path.resolve(__dirname, '../assets/src_tenant_alpha.sh');
37
44
  const scriptPath = options.script ? path.resolve(process.cwd(), options.script) : defaultScriptPath;
38
45
 
39
- if (!fs.existsSync(scriptPath)) {
46
+ const scriptExists = await fs.pathExists(scriptPath);
47
+ if (!scriptExists) {
40
48
  console.log(chalk.red(`❌ Error: Deterministic script not found at ${scriptPath}`));
41
49
  console.log(chalk.yellow(`Expected internal asset at: ${defaultScriptPath}`));
42
50
  return;
@@ -49,7 +57,7 @@ program
49
57
  // 1. SCAFFOLDING INFRA
50
58
  spinner.start('Setting up environment (Vite + TS)...');
51
59
  await fs.ensureDir(targetDir);
52
- await execa('npm', ['create', 'vite@latest', '.', '--', '--template', 'react-ts'], { cwd: targetDir });
60
+ await execa('npm', ['create', 'vite@latest', '.', '--', '--template', 'react-ts'], execOpts(targetDir));
53
61
  spinner.succeed('Environment scaffolded.');
54
62
 
55
63
  // 2. CLEANUP (Piazza pulita per il determinismo)
@@ -67,54 +75,59 @@ program
67
75
  await injectInfraFiles(targetDir, name);
68
76
  spinner.succeed('Infrastructure configured.');
69
77
 
70
- // 4. DETERMINISTIC SRC (Proiezione dal DNA interno)
71
- spinner.start('Executing deterministic src projection...');
72
- const localScript = path.join(targetDir, 'setup_src.sh');
73
- await fs.copy(scriptPath, localScript);
74
- await fs.chmod(localScript, '755');
75
-
76
- // Esecuzione dello script (che ora crea anche index.html se incluso)
77
- await execa('./setup_src.sh', [], { cwd: targetDir, shell: true });
78
- await fs.remove(localScript);
79
- spinner.succeed('Source code and assets projected successfully.');
78
+ // 4. DETERMINISTIC SRC (Node-only projection no shell; Windows-compatible)
79
+ spinner.start('Projecting source files (Node)...');
80
+ const { fileCount } = await projectSrc(targetDir, scriptPath);
81
+ spinner.succeed(`Source code and assets projected (${fileCount} files).`);
80
82
 
81
83
  // 5. DEPENDENCY RESOLUTION (Green Build Enforcement)
82
84
  spinner.start('Installing dependencies (this may take a minute)...');
83
85
 
84
86
  // 5a. Runtime Dependencies (Risolve Radix, CVA e Animations)
85
- await execa('npm', ['install',
86
- 'react',
87
- 'react-dom',
88
- 'zod',
89
- 'react-router-dom',
90
- 'lucide-react',
91
- 'radix-ui', // 🛡️ Risolve TS2307
92
- '@base-ui/react', // Supporto componenti headless
93
- 'class-variance-authority', // Supporto varianti componenti
94
- 'tailwind-merge',
95
- 'clsx',
96
- 'tw-animate-css', // Supporto animazioni tenant
97
- 'file-saver',
98
- 'jszip'
99
- ], { cwd: targetDir });
100
-
87
+ await execa(
88
+ 'npm',
89
+ [
90
+ 'install',
91
+ 'react',
92
+ 'react-dom',
93
+ 'zod',
94
+ 'react-router-dom',
95
+ 'lucide-react',
96
+ 'radix-ui',
97
+ '@base-ui/react',
98
+ 'class-variance-authority',
99
+ 'tailwind-merge',
100
+ 'clsx',
101
+ 'tw-animate-css',
102
+ 'file-saver',
103
+ 'jszip',
104
+ ],
105
+ execOpts(targetDir)
106
+ );
107
+
101
108
  // 5b. Dev Dependencies
102
- await execa('npm', ['install', '-D',
103
- 'vite',
104
- '@vitejs/plugin-react',
105
- 'typescript',
106
- '@tailwindcss/vite',
107
- 'tailwindcss',
108
- '@types/node',
109
- '@types/react',
110
- '@types/react-dom',
111
- '@types/file-saver'
112
- ], { cwd: targetDir });
109
+ await execa(
110
+ 'npm',
111
+ [
112
+ 'install',
113
+ '-D',
114
+ 'vite',
115
+ '@vitejs/plugin-react',
116
+ 'typescript',
117
+ '@tailwindcss/vite',
118
+ 'tailwindcss',
119
+ '@types/node',
120
+ '@types/react',
121
+ '@types/react-dom',
122
+ '@types/file-saver',
123
+ ],
124
+ execOpts(targetDir)
125
+ );
113
126
 
114
127
  // 5c. Linking Core via yalc
115
128
  spinner.text = 'Linking @jsonpages/core via yalc...';
116
129
  try {
117
- await execa('yalc', ['add', '@jsonpages/core'], { cwd: targetDir });
130
+ await execa('yalc', ['add', '@jsonpages/core'], execOpts(targetDir));
118
131
  } catch (e) {
119
132
  spinner.warn(chalk.yellow('Yalc link failed. Ensure "@jsonpages/core" is published in yalc.'));
120
133
  }
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Cross-platform source projection: parses the deterministic shell script
3
+ * (heredoc format) and writes all files via Node.js fs. No shell execution.
4
+ * Single source of truth: assets/src_tenant_alpha.sh
5
+ */
6
+ import fs from 'fs-extra';
7
+ import path from 'path';
8
+ import { fileURLToPath } from 'url';
9
+
10
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
11
+
12
+ const HEREDOC_MARKER = 'END_OF_FILE_CONTENT';
13
+ const HEREDOC_PATTERN = /cat << 'END_OF_FILE_CONTENT' > "([^"]+)"\n/g;
14
+
15
+ /**
16
+ * Parse shell script content (heredoc format) into a map of relativePath -> content.
17
+ * @param {string} shContent - Raw content of src_tenant_alpha.sh
18
+ * @returns {Record<string, string>}
19
+ */
20
+ export function parseShToManifest(shContent) {
21
+ const manifest = Object.create(null);
22
+ let match;
23
+ while ((match = HEREDOC_PATTERN.exec(shContent)) !== null) {
24
+ const filePath = match[1];
25
+ const contentStart = match.index + match[0].length;
26
+ const contentEnd = findHeredocEnd(shContent, contentStart);
27
+ const content =
28
+ contentEnd === -1
29
+ ? shContent.slice(contentStart)
30
+ : shContent.slice(contentStart, contentEnd);
31
+ manifest[filePath] = content;
32
+ }
33
+ return manifest;
34
+ }
35
+
36
+ function findHeredocEnd(text, fromIndex) {
37
+ let i = fromIndex;
38
+ while (i < text.length) {
39
+ const lineEnd = text.indexOf('\n', i);
40
+ const line = lineEnd === -1 ? text.slice(i) : text.slice(i, lineEnd);
41
+ if (line.trim() === HEREDOC_MARKER) {
42
+ return i;
43
+ }
44
+ i = lineEnd === -1 ? text.length : lineEnd + 1;
45
+ }
46
+ return -1;
47
+ }
48
+
49
+ /**
50
+ * Project all tenant source files into targetDir using only Node.js.
51
+ * Reads the script from scriptPath (default: CLI assets/src_tenant_alpha.sh).
52
+ * @param {string} targetDir - Absolute path to the new tenant directory
53
+ * @param {string} [scriptPath] - Optional path to .sh script (default: bundled asset)
54
+ * @returns {Promise<{ fileCount: number }>}
55
+ */
56
+ export async function projectSrc(targetDir, scriptPath) {
57
+ const resolvedPath =
58
+ scriptPath ||
59
+ path.resolve(__dirname, '../assets/src_tenant_alpha.sh');
60
+
61
+ if (!(await fs.pathExists(resolvedPath))) {
62
+ throw new Error(`Projection script not found: ${resolvedPath}`);
63
+ }
64
+
65
+ const shContent = await fs.readFile(resolvedPath, 'utf8');
66
+ const manifest = parseShToManifest(shContent);
67
+ const entries = Object.entries(manifest);
68
+
69
+ for (const [relativePath, content] of entries) {
70
+ const absolutePath = path.join(targetDir, relativePath);
71
+ await fs.ensureDir(path.dirname(absolutePath));
72
+ await fs.writeFile(absolutePath, content, 'utf8');
73
+ }
74
+
75
+ return { fileCount: entries.length };
76
+ }