@contentstorage/core 2.2.0 → 3.0.1
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/dist/commands/cli.js +13 -0
- package/dist/commands/screenshot.d.ts +2 -0
- package/dist/commands/screenshot.js +102 -0
- package/dist/utils/browser.d.ts +9 -0
- package/dist/utils/browser.js +58 -0
- package/dist/utils/flatten-json.d.ts +1 -1
- package/dist/utils/flatten-json.js +2 -37
- package/package.json +2 -2
package/dist/commands/cli.js
CHANGED
|
@@ -3,6 +3,7 @@ import chalk from 'chalk';
|
|
|
3
3
|
import { pullContent } from './pull.js';
|
|
4
4
|
import { generateTypes } from './generate-types.js';
|
|
5
5
|
import { showStats } from './stats.js';
|
|
6
|
+
import { captureScreenshot } from './screenshot.js';
|
|
6
7
|
const COMMANDS = {
|
|
7
8
|
pull: {
|
|
8
9
|
name: 'pull',
|
|
@@ -37,6 +38,15 @@ const COMMANDS = {
|
|
|
37
38
|
' --pending-changes Analyze pending/draft content',
|
|
38
39
|
],
|
|
39
40
|
},
|
|
41
|
+
screenshot: {
|
|
42
|
+
name: 'screenshot',
|
|
43
|
+
description: 'Open browser in live-editor mode for screenshots',
|
|
44
|
+
usage: 'contentstorage screenshot --url <url> [options]',
|
|
45
|
+
options: [
|
|
46
|
+
' --url <url> Dev server URL (e.g., http://localhost:3000)',
|
|
47
|
+
' --content-key <key> Content key for your project',
|
|
48
|
+
],
|
|
49
|
+
},
|
|
40
50
|
};
|
|
41
51
|
function showHelp() {
|
|
42
52
|
console.log(chalk.bold('\nContentstorage CLI'));
|
|
@@ -99,6 +109,9 @@ async function main() {
|
|
|
99
109
|
case 'stats':
|
|
100
110
|
await showStats();
|
|
101
111
|
break;
|
|
112
|
+
case 'screenshot':
|
|
113
|
+
await captureScreenshot();
|
|
114
|
+
break;
|
|
102
115
|
default:
|
|
103
116
|
console.error(chalk.red(`Unknown command: ${command}\n`));
|
|
104
117
|
console.log(chalk.dim('Run "contentstorage --help" for usage'));
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { exec } from 'child_process';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import { loadConfig } from '../core/config-loader.js';
|
|
5
|
+
export async function captureScreenshot() {
|
|
6
|
+
console.log(chalk.blue('Starting screenshot mode...'));
|
|
7
|
+
// Parse CLI arguments
|
|
8
|
+
const config = await parseArguments();
|
|
9
|
+
// Validate configuration
|
|
10
|
+
if (!validateConfig(config)) {
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
// Build URL with live-editor params
|
|
14
|
+
const liveEditorUrl = buildLiveEditorUrl(config.url, config.contentKey);
|
|
15
|
+
console.log(chalk.blue(`Opening browser with live-editor mode...`));
|
|
16
|
+
console.log(chalk.dim(`URL: ${liveEditorUrl}`));
|
|
17
|
+
// Open in default browser
|
|
18
|
+
openInBrowser(liveEditorUrl);
|
|
19
|
+
console.log(chalk.green('\nBrowser opened successfully!'));
|
|
20
|
+
console.log(chalk.dim('Use the Contentstorage UI to capture screenshots.'));
|
|
21
|
+
}
|
|
22
|
+
async function parseArguments() {
|
|
23
|
+
const args = process.argv.slice(2);
|
|
24
|
+
const cliConfig = {};
|
|
25
|
+
for (let i = 0; i < args.length; i++) {
|
|
26
|
+
const arg = args[i];
|
|
27
|
+
if (arg.startsWith('--')) {
|
|
28
|
+
const key = arg.substring(2);
|
|
29
|
+
const value = args[i + 1];
|
|
30
|
+
if (value && !value.startsWith('--')) {
|
|
31
|
+
if (key === 'url') {
|
|
32
|
+
cliConfig.url = value;
|
|
33
|
+
}
|
|
34
|
+
else if (key === 'content-key') {
|
|
35
|
+
cliConfig.contentKey = value;
|
|
36
|
+
}
|
|
37
|
+
i++;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
// Load file config for contentKey fallback
|
|
42
|
+
let fileConfig = {};
|
|
43
|
+
try {
|
|
44
|
+
fileConfig = await loadConfig();
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
console.log(chalk.yellow('Could not load configuration file. Using CLI arguments only.'));
|
|
48
|
+
}
|
|
49
|
+
return {
|
|
50
|
+
url: cliConfig.url || '',
|
|
51
|
+
contentKey: cliConfig.contentKey || fileConfig.contentKey,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
function validateConfig(config) {
|
|
55
|
+
if (!config.url) {
|
|
56
|
+
console.error(chalk.red('Error: --url argument is required.'));
|
|
57
|
+
console.log(chalk.dim('Usage: contentstorage screenshot --url http://localhost:3000'));
|
|
58
|
+
console.log(chalk.dim(' contentstorage screenshot --url http://localhost:5173'));
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
try {
|
|
62
|
+
new URL(config.url);
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
console.error(chalk.red(`Error: Invalid URL format: ${config.url}`));
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
if (!config.contentKey) {
|
|
69
|
+
console.error(chalk.red('Error: content-key is required.'));
|
|
70
|
+
console.log(chalk.dim('Provide via --content-key or in contentstorage.config.js'));
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
function buildLiveEditorUrl(baseUrl, contentKey) {
|
|
76
|
+
const url = new URL(baseUrl);
|
|
77
|
+
url.searchParams.set('contentstorage_live_editor', 'true');
|
|
78
|
+
url.searchParams.set('screenshot_mode', 'true');
|
|
79
|
+
if (contentKey) {
|
|
80
|
+
url.searchParams.set('contentstorage_key', contentKey);
|
|
81
|
+
}
|
|
82
|
+
return url.toString();
|
|
83
|
+
}
|
|
84
|
+
function openInBrowser(url) {
|
|
85
|
+
const platform = process.platform;
|
|
86
|
+
let command;
|
|
87
|
+
if (platform === 'darwin') {
|
|
88
|
+
command = `open "${url}"`;
|
|
89
|
+
}
|
|
90
|
+
else if (platform === 'win32') {
|
|
91
|
+
command = `start "" "${url}"`;
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
command = `xdg-open "${url}"`;
|
|
95
|
+
}
|
|
96
|
+
exec(command, (error) => {
|
|
97
|
+
if (error) {
|
|
98
|
+
console.error(chalk.red(`Failed to open browser: ${error.message}`));
|
|
99
|
+
console.log(chalk.yellow(`Please open this URL manually: ${url}`));
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Find Chrome executable on the system
|
|
3
|
+
* @returns Path to Chrome executable or null if not found
|
|
4
|
+
*/
|
|
5
|
+
export declare function findChrome(): string | null;
|
|
6
|
+
/**
|
|
7
|
+
* Get helpful error message when Chrome is not found
|
|
8
|
+
*/
|
|
9
|
+
export declare function getChromeNotFoundMessage(): string;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
/**
|
|
4
|
+
* Common Chrome installation paths by platform
|
|
5
|
+
*/
|
|
6
|
+
const CHROME_PATHS = {
|
|
7
|
+
darwin: [
|
|
8
|
+
'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
|
|
9
|
+
'/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary',
|
|
10
|
+
'/Applications/Chromium.app/Contents/MacOS/Chromium',
|
|
11
|
+
],
|
|
12
|
+
win32: [
|
|
13
|
+
'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe',
|
|
14
|
+
'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe',
|
|
15
|
+
process.env.LOCALAPPDATA
|
|
16
|
+
? path.join(process.env.LOCALAPPDATA, 'Google\\Chrome\\Application\\chrome.exe')
|
|
17
|
+
: '',
|
|
18
|
+
].filter(Boolean),
|
|
19
|
+
linux: [
|
|
20
|
+
'/usr/bin/google-chrome',
|
|
21
|
+
'/usr/bin/google-chrome-stable',
|
|
22
|
+
'/usr/bin/chromium',
|
|
23
|
+
'/usr/bin/chromium-browser',
|
|
24
|
+
'/snap/bin/chromium',
|
|
25
|
+
],
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Find Chrome executable on the system
|
|
29
|
+
* @returns Path to Chrome executable or null if not found
|
|
30
|
+
*/
|
|
31
|
+
export function findChrome() {
|
|
32
|
+
const platform = process.platform;
|
|
33
|
+
const paths = CHROME_PATHS[platform] || [];
|
|
34
|
+
for (const chromePath of paths) {
|
|
35
|
+
if (chromePath && fs.existsSync(chromePath)) {
|
|
36
|
+
return chromePath;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Get helpful error message when Chrome is not found
|
|
43
|
+
*/
|
|
44
|
+
export function getChromeNotFoundMessage() {
|
|
45
|
+
const platform = process.platform;
|
|
46
|
+
const installInstructions = {
|
|
47
|
+
darwin: 'Download from https://www.google.com/chrome/ or install via: brew install --cask google-chrome',
|
|
48
|
+
win32: 'Download from https://www.google.com/chrome/',
|
|
49
|
+
linux: 'Install via: sudo apt install google-chrome-stable (Debian/Ubuntu) or sudo dnf install google-chrome-stable (Fedora)',
|
|
50
|
+
};
|
|
51
|
+
return `Chrome not found on your system.
|
|
52
|
+
|
|
53
|
+
Searched locations:
|
|
54
|
+
${(CHROME_PATHS[platform] || []).map((p) => ` - ${p}`).join('\n')}
|
|
55
|
+
|
|
56
|
+
To install Chrome:
|
|
57
|
+
${installInstructions[platform] || 'Download from https://www.google.com/chrome/'}`;
|
|
58
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare function flattenJson(data: any, prefix?: string, result?: Record<string,
|
|
1
|
+
export declare function flattenJson(data: any, prefix?: string, result?: Record<string, string>): Record<string, string>;
|
|
@@ -1,56 +1,21 @@
|
|
|
1
1
|
export function flattenJson(data, prefix = '', result = {}) {
|
|
2
|
-
|
|
3
|
-
// Check if the current data is an object that should not be flattened further
|
|
4
|
-
if (typeof data === 'object' &&
|
|
5
|
-
data !== null &&
|
|
6
|
-
!Array.isArray(data) && // Must be an object, not an array
|
|
7
|
-
Object.prototype.hasOwnProperty.call(data, stopFlatteningIfKeyExists)) {
|
|
8
|
-
if (prefix) {
|
|
9
|
-
// If there's a prefix, this object is nested. Assign it directly.
|
|
10
|
-
result[prefix] = data;
|
|
11
|
-
}
|
|
12
|
-
else {
|
|
13
|
-
// This is the root object itself having the 'stopFlatteningIfKeyExists' key.
|
|
14
|
-
// Consistent with how root primitives are handled (result remains empty),
|
|
15
|
-
// we don't add it to the result if there's no prefix. The function's
|
|
16
|
-
// purpose is to flatten *into* key-value pairs. If the root itself
|
|
17
|
-
// is one of these "don't flatten" types, 'result' will remain empty,
|
|
18
|
-
// which is consistent with the original function's behavior for root primitives.
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
else if (typeof data === 'object' && data !== null) {
|
|
22
|
-
// It's an object or array that should be processed further
|
|
2
|
+
if (typeof data === 'object' && data !== null) {
|
|
23
3
|
if (Array.isArray(data)) {
|
|
24
|
-
if (data.length === 0 && prefix) {
|
|
25
|
-
// Handle empty arrays if prefix exists
|
|
26
|
-
result[prefix] = [];
|
|
27
|
-
}
|
|
28
4
|
data.forEach((item, index) => {
|
|
29
|
-
// Recursively call, the check for 'stopFlatteningIfKeyExists' will apply to 'item'
|
|
30
5
|
flattenJson(item, prefix ? `${prefix}.${index}` : `${index}`, result);
|
|
31
6
|
});
|
|
32
7
|
}
|
|
33
8
|
else {
|
|
34
|
-
let isEmptyObject = true;
|
|
35
9
|
for (const key in data) {
|
|
36
10
|
if (Object.prototype.hasOwnProperty.call(data, key)) {
|
|
37
|
-
isEmptyObject = false;
|
|
38
11
|
const newPrefix = prefix ? `${prefix}.${key}` : key;
|
|
39
|
-
// Recursively call, the check for 'stopFlatteningIfKeyExists' will apply to 'data[key]'
|
|
40
12
|
flattenJson(data[key], newPrefix, result);
|
|
41
13
|
}
|
|
42
14
|
}
|
|
43
|
-
if (isEmptyObject && prefix) {
|
|
44
|
-
// Handle empty objects (that were not 'special') if prefix exists
|
|
45
|
-
result[prefix] = {};
|
|
46
|
-
}
|
|
47
15
|
}
|
|
48
16
|
}
|
|
49
17
|
else if (prefix) {
|
|
50
|
-
result[prefix] = data;
|
|
18
|
+
result[prefix] = String(data);
|
|
51
19
|
}
|
|
52
|
-
// If the initial data is a primitive and prefix is empty, result remains empty.
|
|
53
|
-
// If the initial data is a 'special' object (contains 'stopFlatteningIfKeyExists')
|
|
54
|
-
// and prefix is empty, result also remains empty based on the logic above.
|
|
55
20
|
return result;
|
|
56
21
|
}
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@contentstorage/core",
|
|
3
3
|
"author": "Kaido Hussar <kaido@contentstorage.app>",
|
|
4
4
|
"homepage": "https://contentstorage.app",
|
|
5
|
-
"version": "
|
|
5
|
+
"version": "3.0.1",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"description": "Contentstorage CLI for managing translations and generating TypeScript types",
|
|
8
8
|
"license": "MIT",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"release": "npx release-it"
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"axios": "^1.
|
|
26
|
+
"axios": "^1.13.2",
|
|
27
27
|
"chalk": "^4.1.2",
|
|
28
28
|
"pluralize": "^8.0.0"
|
|
29
29
|
},
|