@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 +5 -6
- package/scripts/build-projection-manifest.mjs +59 -0
- package/src/index.js +53 -40
- package/src/projection.js +76 -0
package/package.json
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jsonpages/cli",
|
|
3
|
-
"version": "
|
|
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
|
-
|
|
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'],
|
|
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 (
|
|
71
|
-
spinner.start('
|
|
72
|
-
const
|
|
73
|
-
|
|
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(
|
|
86
|
-
'
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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(
|
|
103
|
-
'
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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'],
|
|
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
|
+
}
|