@papercraneai/cli 1.7.1 → 1.8.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/bin/papercrane.js +145 -7
- package/lib/dev-server.js +7 -5
- package/package.json +1 -1
package/bin/papercrane.js
CHANGED
|
@@ -51,7 +51,23 @@ async function showPostLoginHelp() {
|
|
|
51
51
|
|
|
52
52
|
const workspaceDir = getLocalWorkspacePath(ws.id);
|
|
53
53
|
const { generateScaffolding } = await import('../lib/dev-server.js');
|
|
54
|
-
|
|
54
|
+
|
|
55
|
+
// Check if we can reuse the pre-scaffolded default workspace
|
|
56
|
+
let didSymlink = false;
|
|
57
|
+
try {
|
|
58
|
+
await fs.access(workspaceDir);
|
|
59
|
+
// Workspace dir already exists — normal path
|
|
60
|
+
} catch {
|
|
61
|
+
if (await isDefaultScaffoldAvailable()) {
|
|
62
|
+
await fs.mkdir(path.dirname(workspaceDir), { recursive: true });
|
|
63
|
+
await fs.symlink(DEFAULT_SCAFFOLD_DIR, workspaceDir);
|
|
64
|
+
didSymlink = true;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (!didSymlink) {
|
|
69
|
+
await generateScaffolding(workspaceDir);
|
|
70
|
+
}
|
|
55
71
|
|
|
56
72
|
const currentLink = path.join(os.homedir(), '.papercrane', 'current');
|
|
57
73
|
try {
|
|
@@ -132,6 +148,39 @@ import { createRequire } from 'module';
|
|
|
132
148
|
const require = createRequire(import.meta.url);
|
|
133
149
|
const { version } = require('../package.json');
|
|
134
150
|
|
|
151
|
+
const DEFAULT_SCAFFOLD_DIR = path.join(os.homedir(), '.papercrane', 'workspaces', 'default');
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Check if the pre-scaffolded default workspace is available for symlink reuse.
|
|
155
|
+
* Returns true only if default/ has been scaffolded AND no other workspace already
|
|
156
|
+
* symlinks to it.
|
|
157
|
+
*/
|
|
158
|
+
async function isDefaultScaffoldAvailable() {
|
|
159
|
+
try {
|
|
160
|
+
await fs.access(path.join(DEFAULT_SCAFFOLD_DIR, 'package.json'));
|
|
161
|
+
} catch {
|
|
162
|
+
return false;
|
|
163
|
+
}
|
|
164
|
+
// Check if any existing workspace already claims default/
|
|
165
|
+
const workspacesDir = path.join(os.homedir(), '.papercrane', 'workspaces');
|
|
166
|
+
try {
|
|
167
|
+
const entries = await fs.readdir(workspacesDir);
|
|
168
|
+
for (const entry of entries) {
|
|
169
|
+
if (entry === 'default') continue;
|
|
170
|
+
const entryPath = path.join(workspacesDir, entry);
|
|
171
|
+
try {
|
|
172
|
+
const target = await fs.readlink(entryPath);
|
|
173
|
+
if (target === DEFAULT_SCAFFOLD_DIR) return false;
|
|
174
|
+
} catch {
|
|
175
|
+
// Not a symlink — ignore
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
} catch {
|
|
179
|
+
// workspaces dir doesn't exist yet — that's fine
|
|
180
|
+
}
|
|
181
|
+
return true;
|
|
182
|
+
}
|
|
183
|
+
|
|
135
184
|
/**
|
|
136
185
|
* Run the browser-based auth flow (init session, open browser or print URL, poll for result).
|
|
137
186
|
* @param {Object} options
|
|
@@ -387,6 +436,45 @@ program
|
|
|
387
436
|
}
|
|
388
437
|
});
|
|
389
438
|
|
|
439
|
+
// ============================================================================
|
|
440
|
+
// SCAFFOLD COMMAND
|
|
441
|
+
// ============================================================================
|
|
442
|
+
|
|
443
|
+
program
|
|
444
|
+
.command('scaffold')
|
|
445
|
+
.description('Pre-scaffold a workspace directory (no login required)')
|
|
446
|
+
.option('--dir <path>', 'Directory to scaffold', DEFAULT_SCAFFOLD_DIR)
|
|
447
|
+
.option('--npm-install', 'Run npm install after scaffolding')
|
|
448
|
+
.action(async (options) => {
|
|
449
|
+
try {
|
|
450
|
+
const dir = options.dir;
|
|
451
|
+
console.log(chalk.cyan(`Scaffolding workspace in ${dir}...`));
|
|
452
|
+
|
|
453
|
+
const { generateScaffolding } = await import('../lib/dev-server.js');
|
|
454
|
+
await generateScaffolding(dir, { skipInstall: !options.npmInstall });
|
|
455
|
+
|
|
456
|
+
// Set ~/.papercrane/current symlink → scaffolded dir
|
|
457
|
+
const currentLink = path.join(os.homedir(), '.papercrane', 'current');
|
|
458
|
+
try {
|
|
459
|
+
const existing = await fs.readlink(currentLink);
|
|
460
|
+
if (existing !== dir) {
|
|
461
|
+
await fs.unlink(currentLink);
|
|
462
|
+
await fs.symlink(dir, currentLink);
|
|
463
|
+
}
|
|
464
|
+
} catch {
|
|
465
|
+
try { await fs.unlink(currentLink); } catch {}
|
|
466
|
+
await fs.symlink(dir, currentLink);
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
console.log(chalk.green(`\n✓ Scaffolded workspace`));
|
|
470
|
+
console.log(chalk.dim(` ${currentLink} → ${dir}`));
|
|
471
|
+
console.log(chalk.dim(` Dashboards: ${dir}/app/\n`));
|
|
472
|
+
} catch (error) {
|
|
473
|
+
console.error(chalk.red('Error:'), error.message);
|
|
474
|
+
process.exit(1);
|
|
475
|
+
}
|
|
476
|
+
});
|
|
477
|
+
|
|
390
478
|
// ============================================================================
|
|
391
479
|
// FUNCTION COMMANDS
|
|
392
480
|
// ============================================================================
|
|
@@ -755,7 +843,6 @@ workspacesCmd
|
|
|
755
843
|
|
|
756
844
|
await setDefaultWorkspace(wsId);
|
|
757
845
|
|
|
758
|
-
// Scaffold the workspace and create ~/.papercrane/current symlink
|
|
759
846
|
const { generateScaffolding, resetScaffolding } = await import('../lib/dev-server.js');
|
|
760
847
|
const workspaceDir = getLocalWorkspacePath(wsId);
|
|
761
848
|
|
|
@@ -764,8 +851,27 @@ workspacesCmd
|
|
|
764
851
|
await fs.rm(path.join(workspaceDir, '.next'), { recursive: true, force: true });
|
|
765
852
|
console.log(chalk.dim('Scaffolding regenerated.'));
|
|
766
853
|
}
|
|
767
|
-
await generateScaffolding(workspaceDir);
|
|
768
854
|
|
|
855
|
+
// Check if we can reuse the pre-scaffolded default workspace
|
|
856
|
+
let didSymlink = false;
|
|
857
|
+
try {
|
|
858
|
+
await fs.access(workspaceDir);
|
|
859
|
+
// Workspace dir already exists — normal path
|
|
860
|
+
} catch {
|
|
861
|
+
// Workspace dir doesn't exist yet — check for pre-scaffolded default
|
|
862
|
+
if (await isDefaultScaffoldAvailable()) {
|
|
863
|
+
await fs.mkdir(path.dirname(workspaceDir), { recursive: true });
|
|
864
|
+
await fs.symlink(DEFAULT_SCAFFOLD_DIR, workspaceDir);
|
|
865
|
+
didSymlink = true;
|
|
866
|
+
console.log(chalk.dim('Reusing pre-scaffolded workspace (symlink).'));
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
if (!didSymlink) {
|
|
871
|
+
await generateScaffolding(workspaceDir);
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
// Update ~/.papercrane/current symlink
|
|
769
875
|
const currentLink = path.join(os.homedir(), '.papercrane', 'current');
|
|
770
876
|
try {
|
|
771
877
|
const existing = await fs.readlink(currentLink);
|
|
@@ -795,16 +901,33 @@ workspacesCmd
|
|
|
795
901
|
program
|
|
796
902
|
.command('dev')
|
|
797
903
|
.description('Start local dev server for dashboard development with HMR')
|
|
798
|
-
.option('-w, --workspace <id>', 'Workspace ID')
|
|
904
|
+
.option('-w, --workspace <id>', 'Workspace ID (resolves to workspaces/<id>/ instead of current)')
|
|
799
905
|
.option('-p, --port <port>', 'Port (default: 3100)', '3100')
|
|
906
|
+
.option('--npm-install', 'Force run npm install before starting')
|
|
800
907
|
.option('--clear-cache', 'Clear build cache before starting (use after structural changes like editing globals.css or adding dependencies)')
|
|
801
908
|
.option('--reset-scaffolding', 'Regenerate scaffolding files (layout.tsx, globals.css, configs) and clear build cache')
|
|
802
909
|
.action(async (options) => {
|
|
803
910
|
try {
|
|
804
911
|
const { generateScaffolding, resetScaffolding, startDevServer } = await import('../lib/dev-server.js');
|
|
912
|
+
const { execSync } = await import('child_process');
|
|
805
913
|
|
|
806
|
-
|
|
807
|
-
|
|
914
|
+
let workspaceDir;
|
|
915
|
+
if (options.workspace) {
|
|
916
|
+
workspaceDir = getLocalWorkspacePath(parseInt(options.workspace, 10));
|
|
917
|
+
} else {
|
|
918
|
+
// Resolve from ~/.papercrane/current symlink (no login required)
|
|
919
|
+
const currentLink = path.join(os.homedir(), '.papercrane', 'current');
|
|
920
|
+
try {
|
|
921
|
+
await fs.readlink(currentLink);
|
|
922
|
+
workspaceDir = currentLink;
|
|
923
|
+
} catch {
|
|
924
|
+
console.error(chalk.red('No workspace set up.'));
|
|
925
|
+
console.error(chalk.dim('\nRun one of:'));
|
|
926
|
+
console.error(chalk.dim(' papercrane scaffold # Pre-scaffold without login'));
|
|
927
|
+
console.error(chalk.dim(' papercrane workspaces use <id> # Set active workspace'));
|
|
928
|
+
process.exit(1);
|
|
929
|
+
}
|
|
930
|
+
}
|
|
808
931
|
|
|
809
932
|
if (options.resetScaffolding) {
|
|
810
933
|
await resetScaffolding(workspaceDir);
|
|
@@ -817,7 +940,21 @@ program
|
|
|
817
940
|
console.log(chalk.dim('Build cache cleared.'));
|
|
818
941
|
}
|
|
819
942
|
|
|
820
|
-
await generateScaffolding(workspaceDir);
|
|
943
|
+
await generateScaffolding(workspaceDir, { skipInstall: true });
|
|
944
|
+
|
|
945
|
+
// npm install — dev server won't work without node_modules
|
|
946
|
+
const nmDir = path.join(workspaceDir, 'node_modules');
|
|
947
|
+
if (options.npmInstall) {
|
|
948
|
+
console.log(chalk.dim('Running npm install...'));
|
|
949
|
+
execSync('npm install --prefer-offline', { cwd: workspaceDir, stdio: 'inherit' });
|
|
950
|
+
} else {
|
|
951
|
+
try {
|
|
952
|
+
await fs.access(nmDir);
|
|
953
|
+
} catch {
|
|
954
|
+
console.log(chalk.dim('node_modules not found, running npm install...'));
|
|
955
|
+
execSync('npm install --prefer-offline', { cwd: workspaceDir, stdio: 'inherit' });
|
|
956
|
+
}
|
|
957
|
+
}
|
|
821
958
|
|
|
822
959
|
const port = parseInt(options.port, 10);
|
|
823
960
|
const dashboardDir = workspaceDir + '/app';
|
|
@@ -933,6 +1070,7 @@ program.action(async (_, cmd) => {
|
|
|
933
1070
|
console.error(' papercrane call <path> [json] Call an endpoint');
|
|
934
1071
|
console.error(' papercrane add Add a new integration');
|
|
935
1072
|
console.error(' papercrane dashboard-guide Show how to build dashboards');
|
|
1073
|
+
console.error(' papercrane scaffold Pre-scaffold workspace (no login required)');
|
|
936
1074
|
console.error(' papercrane workspaces list List workspaces');
|
|
937
1075
|
console.error(' papercrane workspaces use <id> Set default workspace');
|
|
938
1076
|
console.error(' papercrane dev Start local dev server with HMR');
|
package/lib/dev-server.js
CHANGED
|
@@ -259,7 +259,7 @@ export async function resetScaffolding(workspaceDir) {
|
|
|
259
259
|
* Creates package.json, tsconfig.json, postcss.config.mjs, app/layout.tsx, app/globals.css.
|
|
260
260
|
* Runs npm install if node_modules doesn't exist.
|
|
261
261
|
*/
|
|
262
|
-
export async function generateScaffolding(workspaceDir) {
|
|
262
|
+
export async function generateScaffolding(workspaceDir, { skipInstall = false } = {}) {
|
|
263
263
|
const appDir = path.join(workspaceDir, 'app');
|
|
264
264
|
await fs.mkdir(appDir, { recursive: true });
|
|
265
265
|
|
|
@@ -346,10 +346,12 @@ export default nextConfig;
|
|
|
346
346
|
await fs.writeFile(nextConfigPath, nextConfig, 'utf-8');
|
|
347
347
|
|
|
348
348
|
// npm install — only if node_modules doesn't exist yet
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
349
|
+
if (!skipInstall) {
|
|
350
|
+
const nmDir = path.join(workspaceDir, 'node_modules');
|
|
351
|
+
if (!fsSync.existsSync(nmDir)) {
|
|
352
|
+
console.log('Installing dependencies...');
|
|
353
|
+
execSync('npm install --prefer-offline', { cwd: workspaceDir, stdio: 'inherit' });
|
|
354
|
+
}
|
|
353
355
|
}
|
|
354
356
|
}
|
|
355
357
|
|