sandstone-cli 2.0.8 → 2.1.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/lib/create.js +8568 -17
- package/lib/index.js +41151 -80
- package/package.json +3 -2
- package/scripts/version.ts +4 -0
- package/src/commands/create.ts +2 -1
- package/src/commands/watch.ts +16 -5
- package/src/create.ts +12 -13
- package/src/index.ts +38 -44
- package/src/shared.ts +65 -23
- package/src/stubs/react-devtools-core.js +1 -0
- package/src/ui/WatchUI.tsx +2 -0
- package/src/version.ts +1 -0
- package/tsconfig.json +5 -3
- package/lib/commands/build.d.ts +0 -34
- package/lib/commands/build.js +0 -664
- package/lib/commands/create.d.ts +0 -8
- package/lib/commands/create.js +0 -162
- package/lib/commands/dependency.d.ts +0 -4
- package/lib/commands/dependency.js +0 -246
- package/lib/commands/index.d.ts +0 -4
- package/lib/commands/index.js +0 -4
- package/lib/commands/watch.d.ts +0 -6
- package/lib/commands/watch.js +0 -279
- package/lib/create.d.ts +0 -2
- package/lib/index.d.ts +0 -2
- package/lib/shared.d.ts +0 -1
- package/lib/shared.js +0 -20
- package/lib/ui/WatchUI.d.ts +0 -9
- package/lib/ui/WatchUI.js +0 -183
- package/lib/ui/logger.d.ts +0 -20
- package/lib/ui/logger.js +0 -189
- package/lib/ui/types.d.ts +0 -26
- package/lib/ui/types.js +0 -1
- package/lib/utils.d.ts +0 -34
- package/lib/utils.js +0 -105
package/lib/ui/logger.js
DELETED
|
@@ -1,189 +0,0 @@
|
|
|
1
|
-
import fs from 'fs-extra';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import { stripVTControlCharacters, format } from 'util';
|
|
4
|
-
let logPath = null;
|
|
5
|
-
let liveLogCallback = null;
|
|
6
|
-
let liveLogBuffer = [];
|
|
7
|
-
let liveLogReady = false;
|
|
8
|
-
let silent = false;
|
|
9
|
-
// Track initialization and pending writes
|
|
10
|
-
let initPromise = null;
|
|
11
|
-
let writer = null;
|
|
12
|
-
const pendingWrites = [];
|
|
13
|
-
export function initLogger(rootFolder) {
|
|
14
|
-
logPath = path.join(rootFolder, '.sandstone', 'watch.log');
|
|
15
|
-
// Start logWorkerInit detached
|
|
16
|
-
initPromise = logWorkerInit();
|
|
17
|
-
// Return async function that awaits logWorkerFinish
|
|
18
|
-
return () => logWorkerFinish();
|
|
19
|
-
}
|
|
20
|
-
/**
|
|
21
|
-
* Initialize the logger without file writing.
|
|
22
|
-
* Use this for `sand build` where we want logging but no persistent log file.
|
|
23
|
-
*/
|
|
24
|
-
export function initLoggerNoFile() {
|
|
25
|
-
logPath = null;
|
|
26
|
-
liveLogReady = true;
|
|
27
|
-
// Set a default callback that prints to console
|
|
28
|
-
liveLogCallback = (level, args) => {
|
|
29
|
-
console.log(...(level ? [`[${level}]`] : []), ...args);
|
|
30
|
-
};
|
|
31
|
-
}
|
|
32
|
-
/**
|
|
33
|
-
* Set whether the logger should suppress live output.
|
|
34
|
-
*/
|
|
35
|
-
export function setSilent(value) {
|
|
36
|
-
silent = value;
|
|
37
|
-
}
|
|
38
|
-
export function setLiveLogCallback(callback) {
|
|
39
|
-
liveLogCallback = callback;
|
|
40
|
-
}
|
|
41
|
-
export function drainLiveLogBuffer() {
|
|
42
|
-
liveLogReady = true;
|
|
43
|
-
if (liveLogCallback && liveLogBuffer.length > 0) {
|
|
44
|
-
for (const { level, args } of liveLogBuffer) {
|
|
45
|
-
liveLogCallback(level, args);
|
|
46
|
-
}
|
|
47
|
-
liveLogBuffer = [];
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
async function logWorkerInit() {
|
|
51
|
-
await fs.ensureDir(path.dirname(logPath));
|
|
52
|
-
await fs.writeFile(logPath, `=== Watch started at ${new Date().toISOString()} ===\n`);
|
|
53
|
-
writer = fs.createWriteStream(logPath, { flags: 'a' });
|
|
54
|
-
// Wait for the stream to be ready before allowing writes
|
|
55
|
-
await new Promise((resolve, reject) => {
|
|
56
|
-
writer.once('open', () => resolve());
|
|
57
|
-
writer.once('error', reject);
|
|
58
|
-
});
|
|
59
|
-
}
|
|
60
|
-
async function logWorkerMain(level, ...args) {
|
|
61
|
-
// Await logWorkerInit finishing if it isn't finished
|
|
62
|
-
if (initPromise) {
|
|
63
|
-
await initPromise;
|
|
64
|
-
}
|
|
65
|
-
// Skip empty log calls
|
|
66
|
-
if (args.length === 0) {
|
|
67
|
-
return;
|
|
68
|
-
}
|
|
69
|
-
// Skip logs that are just empty strings
|
|
70
|
-
if (args.length === 1 && typeof args[0] === 'string' && stripVTControlCharacters(args[0]).trim() === '') {
|
|
71
|
-
return;
|
|
72
|
-
}
|
|
73
|
-
// Collect all chunks first
|
|
74
|
-
const chunks = [];
|
|
75
|
-
// Timestamp and level prefix
|
|
76
|
-
chunks.push(`[${new Date().toISOString()}]${level !== false ? ` [${level}]` : ''} `);
|
|
77
|
-
// Process each argument
|
|
78
|
-
for (let i = 0; i < args.length; i++) {
|
|
79
|
-
const arg = args[i];
|
|
80
|
-
if (typeof arg === 'string') {
|
|
81
|
-
chunks.push(stripVTControlCharacters(arg));
|
|
82
|
-
}
|
|
83
|
-
else if (Buffer.isBuffer(arg) || arg instanceof Uint8Array) {
|
|
84
|
-
chunks.push(arg);
|
|
85
|
-
}
|
|
86
|
-
else if (arg instanceof ArrayBuffer) {
|
|
87
|
-
chunks.push(new Uint8Array(arg));
|
|
88
|
-
}
|
|
89
|
-
else if (arg instanceof Blob) {
|
|
90
|
-
chunks.push(new Uint8Array(await arg.arrayBuffer()));
|
|
91
|
-
}
|
|
92
|
-
else {
|
|
93
|
-
// Use util.format for objects, numbers, etc.
|
|
94
|
-
chunks.push(format('%O', arg));
|
|
95
|
-
}
|
|
96
|
-
// Add space between args (but not after last)
|
|
97
|
-
if (i < args.length - 1) {
|
|
98
|
-
chunks.push(' ');
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
chunks.push('\n');
|
|
102
|
-
// Concatenate all chunks into a single buffer for atomic write
|
|
103
|
-
const buffers = chunks.map(chunk => typeof chunk === 'string' ? Buffer.from(chunk) : Buffer.from(chunk));
|
|
104
|
-
await writeChunk(Buffer.concat(buffers));
|
|
105
|
-
}
|
|
106
|
-
function writeChunk(chunk) {
|
|
107
|
-
return new Promise((resolve, reject) => {
|
|
108
|
-
if (!writer) {
|
|
109
|
-
reject(new Error('Writer not initialized'));
|
|
110
|
-
return;
|
|
111
|
-
}
|
|
112
|
-
writer.write(chunk, (err) => {
|
|
113
|
-
if (err)
|
|
114
|
-
reject(err);
|
|
115
|
-
else
|
|
116
|
-
resolve();
|
|
117
|
-
});
|
|
118
|
-
});
|
|
119
|
-
}
|
|
120
|
-
async function logWorkerFinish() {
|
|
121
|
-
// Make sure to await logWorkerInit being finished
|
|
122
|
-
if (initPromise) {
|
|
123
|
-
await initPromise;
|
|
124
|
-
}
|
|
125
|
-
// Make sure to await all pending logWorkerMain calls
|
|
126
|
-
await Promise.all(pendingWrites);
|
|
127
|
-
// Close the writer
|
|
128
|
-
if (writer) {
|
|
129
|
-
await new Promise((resolve, reject) => {
|
|
130
|
-
writer.close((err) => {
|
|
131
|
-
if (err)
|
|
132
|
-
reject(err);
|
|
133
|
-
else
|
|
134
|
-
resolve();
|
|
135
|
-
});
|
|
136
|
-
});
|
|
137
|
-
writer = null;
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
function writeLog(level, ...args) {
|
|
141
|
-
if (!silent) {
|
|
142
|
-
if (liveLogReady) {
|
|
143
|
-
liveLogCallback?.(level, args);
|
|
144
|
-
}
|
|
145
|
-
else {
|
|
146
|
-
liveLogBuffer.push({ level, args });
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
if (logPath) {
|
|
150
|
-
// Call logWorkerMain detached and track the promise
|
|
151
|
-
const writePromise = logWorkerMain(level, ...args);
|
|
152
|
-
pendingWrites.push(writePromise);
|
|
153
|
-
// Clean up completed promises to avoid memory leak
|
|
154
|
-
writePromise
|
|
155
|
-
.catch((err) => {
|
|
156
|
-
// Log to stderr so we can see file write errors
|
|
157
|
-
process.stderr.write(`[logger] Write error: ${err}\n`);
|
|
158
|
-
})
|
|
159
|
-
.finally(() => {
|
|
160
|
-
const idx = pendingWrites.indexOf(writePromise);
|
|
161
|
-
if (idx !== -1)
|
|
162
|
-
pendingWrites.splice(idx, 1);
|
|
163
|
-
});
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
export function log(...args) {
|
|
167
|
-
writeLog(false, ...args);
|
|
168
|
-
}
|
|
169
|
-
export function logInfo(...args) {
|
|
170
|
-
writeLog('INFO', ...args);
|
|
171
|
-
}
|
|
172
|
-
export function logWarn(...args) {
|
|
173
|
-
writeLog('WARN', ...args);
|
|
174
|
-
}
|
|
175
|
-
export function logDebug(...args) {
|
|
176
|
-
writeLog('DEBUG', ...args);
|
|
177
|
-
}
|
|
178
|
-
export function logTrace(...args) {
|
|
179
|
-
writeLog('TRACE', ...args);
|
|
180
|
-
}
|
|
181
|
-
export function logError(error) {
|
|
182
|
-
if (typeof error === 'string') {
|
|
183
|
-
writeLog('ERROR', error);
|
|
184
|
-
}
|
|
185
|
-
else {
|
|
186
|
-
const err = error;
|
|
187
|
-
writeLog('ERROR', err?.message || String(error), ...(err?.stack ? ['\n', err.stack] : []));
|
|
188
|
-
}
|
|
189
|
-
}
|
package/lib/ui/types.d.ts
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import type { SandstoneConfig, SandstonePack } from "sandstone";
|
|
2
|
-
export type WatchStatus = 'watching' | 'building' | 'restarting' | 'error' | 'pending';
|
|
3
|
-
export type ChangeCategory = 'src' | 'resources' | 'config' | 'dependencies' | 'other';
|
|
4
|
-
export interface TrackedChange {
|
|
5
|
-
path: string;
|
|
6
|
-
category: ChangeCategory;
|
|
7
|
-
}
|
|
8
|
-
export interface ResourceCounts {
|
|
9
|
-
functions: number;
|
|
10
|
-
other: number;
|
|
11
|
-
}
|
|
12
|
-
export interface BuildResult {
|
|
13
|
-
success: boolean;
|
|
14
|
-
error?: string;
|
|
15
|
-
resourceCounts: ResourceCounts;
|
|
16
|
-
timestamp: number;
|
|
17
|
-
sandstoneConfig?: SandstoneConfig;
|
|
18
|
-
sandstonePack?: SandstonePack;
|
|
19
|
-
resetSandstonePack?: () => void;
|
|
20
|
-
}
|
|
21
|
-
export interface WatchUIAPI {
|
|
22
|
-
setStatus: (status: WatchStatus, reason?: string) => void;
|
|
23
|
-
setChangedFiles: (files: TrackedChange[]) => void;
|
|
24
|
-
setBuildResult: (result: BuildResult) => void;
|
|
25
|
-
setLiveLog: (level: string | false, args: unknown[]) => void;
|
|
26
|
-
}
|
package/lib/ui/types.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/lib/utils.d.ts
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
/** Normalize a path to use forward slashes */
|
|
2
|
-
export declare const normalizePath: (p: string) => string;
|
|
3
|
-
export declare function hasYarn(): boolean;
|
|
4
|
-
export declare function hasPnpm(): boolean;
|
|
5
|
-
export declare function hasBun(): boolean;
|
|
6
|
-
export declare const capitalize: (s: string) => string;
|
|
7
|
-
/**
|
|
8
|
-
* Check if symlinks can be used on this system.
|
|
9
|
-
* Returns true on non-Windows platforms.
|
|
10
|
-
* On Windows, tests if symlinking actually works (requires admin or developer mode).
|
|
11
|
-
*/
|
|
12
|
-
export declare function canUseSymlinks(): Promise<boolean>;
|
|
13
|
-
/**
|
|
14
|
-
* Get the .minecraft path
|
|
15
|
-
*/
|
|
16
|
-
export declare function getMinecraftPath(): string;
|
|
17
|
-
export declare function getWorldsList(clientPath?: string): string[];
|
|
18
|
-
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never;
|
|
19
|
-
type LastOf<T> = UnionToIntersection<T extends any ? () => T : never> extends () => (infer R) ? R : never;
|
|
20
|
-
type Push<T extends any[], V> = [...T, V];
|
|
21
|
-
type UnionToTuple<T, L = LastOf<T>, N = [T] extends [never] ? true : false> = true extends N ? [] : Push<UnionToTuple<Exclude<T, L>>, L>;
|
|
22
|
-
type PowerSet<T, Keys extends any[] = UnionToTuple<keyof T>> = Keys extends [infer Head, ...infer Rest] ? PowerSet<T, Rest> | (Head extends keyof T ? {
|
|
23
|
-
[K in Head]: NonNullable<T[K]>;
|
|
24
|
-
} & PowerSet<T, Rest> : never) : Record<string, never>;
|
|
25
|
-
type Prettify<T> = {
|
|
26
|
-
[K in keyof T]: T[K];
|
|
27
|
-
} & {};
|
|
28
|
-
/**
|
|
29
|
-
* Helper to add key-value pairs to an object if the values are not undefined.
|
|
30
|
-
*
|
|
31
|
-
* @returns An object with the key-value pairs if the values are not undefined, otherwise an empty object.
|
|
32
|
-
*/
|
|
33
|
-
export declare function add<O extends Record<string, any>>(obj: O): Prettify<PowerSet<O>>;
|
|
34
|
-
export {};
|
package/lib/utils.js
DELETED
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
import fs from 'fs';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import os from 'os';
|
|
4
|
-
import { execSync } from 'child_process';
|
|
5
|
-
/** Normalize a path to use forward slashes */
|
|
6
|
-
export const normalizePath = (p) => p.replaceAll('\\', '/');
|
|
7
|
-
export function hasYarn() {
|
|
8
|
-
try {
|
|
9
|
-
execSync('yarn --version');
|
|
10
|
-
return true;
|
|
11
|
-
}
|
|
12
|
-
catch (error) {
|
|
13
|
-
return false;
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
export function hasPnpm() {
|
|
17
|
-
try {
|
|
18
|
-
execSync('pnpm --version');
|
|
19
|
-
return true;
|
|
20
|
-
}
|
|
21
|
-
catch (error) {
|
|
22
|
-
return false;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
export function hasBun() {
|
|
26
|
-
try {
|
|
27
|
-
execSync('bun --version');
|
|
28
|
-
return true;
|
|
29
|
-
}
|
|
30
|
-
catch (error) {
|
|
31
|
-
return false;
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
export const capitalize = (s) => s.charAt(0).toUpperCase() + s.slice(1);
|
|
35
|
-
/**
|
|
36
|
-
* Check if symlinks can be used on this system.
|
|
37
|
-
* Returns true on non-Windows platforms.
|
|
38
|
-
* On Windows, tests if symlinking actually works (requires admin or developer mode).
|
|
39
|
-
*/
|
|
40
|
-
export async function canUseSymlinks() {
|
|
41
|
-
if (os.platform() !== 'win32') {
|
|
42
|
-
return true;
|
|
43
|
-
}
|
|
44
|
-
const testDir = path.join(os.tmpdir(), `symlink-test-${Date.now()}`);
|
|
45
|
-
const targetPath = path.join(testDir, 'target');
|
|
46
|
-
const linkPath = path.join(testDir, 'link');
|
|
47
|
-
try {
|
|
48
|
-
await fs.promises.mkdir(testDir);
|
|
49
|
-
await fs.promises.mkdir(targetPath);
|
|
50
|
-
await fs.promises.writeFile(path.join(targetPath, 'test.txt'), 'symlink-test');
|
|
51
|
-
await fs.promises.symlink(targetPath, linkPath, 'dir');
|
|
52
|
-
// Verify the symlink actually works by reading through it
|
|
53
|
-
const content = await fs.promises.readFile(path.join(linkPath, 'test.txt'), 'utf-8');
|
|
54
|
-
return content === 'symlink-test';
|
|
55
|
-
}
|
|
56
|
-
catch {
|
|
57
|
-
return false;
|
|
58
|
-
}
|
|
59
|
-
finally {
|
|
60
|
-
await fs.promises.rm(testDir, { recursive: true, force: true }).catch(() => { });
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
/**
|
|
64
|
-
* Get the .minecraft path
|
|
65
|
-
*/
|
|
66
|
-
export function getMinecraftPath() {
|
|
67
|
-
function getMCPath() {
|
|
68
|
-
switch (os.platform()) {
|
|
69
|
-
case 'win32':
|
|
70
|
-
return path.join(os.homedir(), 'AppData/Roaming/.minecraft');
|
|
71
|
-
case 'darwin':
|
|
72
|
-
return path.join(os.homedir(), 'Library/Application Support/minecraft');
|
|
73
|
-
case 'linux':
|
|
74
|
-
default:
|
|
75
|
-
return path.join(os.homedir(), '.minecraft');
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
const mcPath = getMCPath();
|
|
79
|
-
if (!fs.existsSync(mcPath)) {
|
|
80
|
-
throw new Error('Unable to locate the .minecraft folder. Please specify it manually.');
|
|
81
|
-
}
|
|
82
|
-
return mcPath;
|
|
83
|
-
}
|
|
84
|
-
export function getWorldsList(clientPath) {
|
|
85
|
-
const mcPath = clientPath || getMinecraftPath();
|
|
86
|
-
const savesPath = path.join(mcPath, 'saves');
|
|
87
|
-
return fs.readdirSync(savesPath, { withFileTypes: true }).filter((f) => f.isDirectory).map((f) => f.name);
|
|
88
|
-
}
|
|
89
|
-
/**
|
|
90
|
-
* Helper to add key-value pairs to an object if the values are not undefined.
|
|
91
|
-
*
|
|
92
|
-
* @returns An object with the key-value pairs if the values are not undefined, otherwise an empty object.
|
|
93
|
-
*/
|
|
94
|
-
export function add(obj) {
|
|
95
|
-
const filtered = {};
|
|
96
|
-
for (const key of Object.keys(obj)) {
|
|
97
|
-
const value = obj[key];
|
|
98
|
-
if (value !== undefined) {
|
|
99
|
-
// @ts-ignore
|
|
100
|
-
filtered[key] = value;
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
// @ts-ignore
|
|
104
|
-
return filtered;
|
|
105
|
-
}
|