castle-web-cli 0.4.37 → 0.4.39
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/buildArchive.d.ts +1 -0
- package/dist/buildArchive.js +108 -0
- package/dist/init.js +106 -63
- package/package.json +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function buildKitArchives(kits?: string[]): void;
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { execFileSync } from 'child_process';
|
|
2
|
+
import * as fs from 'fs';
|
|
3
|
+
import * as os from 'os';
|
|
4
|
+
import * as path from 'path';
|
|
5
|
+
import { ARCHIVE_NAME, PUBLISHED_SDK_VERSION, copyKitSource, getKitArchivePath, rewriteKitPackageJson, } from './init.js';
|
|
6
|
+
import { getKitsDir, getSdkPackagePath, toPosixPath } from './localPaths.js';
|
|
7
|
+
// Refs forced to "published" so the archived deck looks like one scaffolded
|
|
8
|
+
// from a globally-installed castle-web (registry sdk + `castle-web` binary),
|
|
9
|
+
// regardless of the workspace checkout we generate it from.
|
|
10
|
+
const PUBLISHED_REFS = {
|
|
11
|
+
workspaceMode: false,
|
|
12
|
+
sdkRef: `^${PUBLISHED_SDK_VERSION}`,
|
|
13
|
+
cliCommand: 'castle-web',
|
|
14
|
+
cliDistAbs: null,
|
|
15
|
+
sdkPathPosix: null,
|
|
16
|
+
};
|
|
17
|
+
// Build the local sdk and `npm pack` it into a temp dir, returning the tarball
|
|
18
|
+
// path. Installing a tarball (not a `file:` dir) gives the archive a real copy
|
|
19
|
+
// of the sdk in node_modules instead of a symlink to the local checkout.
|
|
20
|
+
function packSdk() {
|
|
21
|
+
const sdkDir = getSdkPackagePath();
|
|
22
|
+
if (!fs.existsSync(sdkDir)) {
|
|
23
|
+
throw new Error(`sdk/ not found at ${sdkDir}; build-archives must run from a workspace checkout.`);
|
|
24
|
+
}
|
|
25
|
+
console.log('Building sdk...');
|
|
26
|
+
execFileSync('npm', ['run', 'build'], { cwd: sdkDir, stdio: 'inherit' });
|
|
27
|
+
const dest = fs.mkdtempSync(path.join(os.tmpdir(), 'castle-sdk-pack-'));
|
|
28
|
+
console.log('Packing sdk...');
|
|
29
|
+
const out = execFileSync('npm', ['pack', '--pack-destination', dest], {
|
|
30
|
+
cwd: sdkDir,
|
|
31
|
+
encoding: 'utf8',
|
|
32
|
+
}).trim();
|
|
33
|
+
// npm pack prints the created tarball filename on its last line.
|
|
34
|
+
const file = out.split('\n').pop().trim();
|
|
35
|
+
return path.join(dest, file);
|
|
36
|
+
}
|
|
37
|
+
// Find every real kit under kits/ (a subdir with a package.json).
|
|
38
|
+
function discoverKits(kitsDir) {
|
|
39
|
+
return fs.readdirSync(kitsDir).filter((name) => {
|
|
40
|
+
const dir = path.join(kitsDir, name);
|
|
41
|
+
return fs.statSync(dir).isDirectory() && fs.existsSync(path.join(dir, 'package.json'));
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
// Generate one ready-to-serve archive for `kit`: kit source (minus build junk)
|
|
45
|
+
// + installed node_modules + a published-mode package.json, gzipped into the
|
|
46
|
+
// bundled kit dir (cli/kits/<kit>/published-deck.tgz).
|
|
47
|
+
function buildKitArchive(kit, sdkTarball) {
|
|
48
|
+
const kitDir = path.join(getKitsDir(), kit);
|
|
49
|
+
if (!fs.existsSync(path.join(kitDir, 'package.json'))) {
|
|
50
|
+
throw new Error(`Kit "${kit}" has no package.json at ${kitDir}.`);
|
|
51
|
+
}
|
|
52
|
+
const stagingRoot = fs.mkdtempSync(path.join(os.tmpdir(), `castle-archive-${kit}-`));
|
|
53
|
+
const deck = path.join(stagingRoot, 'deck');
|
|
54
|
+
try {
|
|
55
|
+
// Same filter the live scaffold uses, plus drop any prior archive.
|
|
56
|
+
copyKitSource(kitDir, deck, new Set([ARCHIVE_NAME]));
|
|
57
|
+
// A stale workspace lock would pin file:../../sdk; drop it before install.
|
|
58
|
+
fs.rmSync(path.join(deck, 'package-lock.json'), { force: true });
|
|
59
|
+
// Published-mode package.json, but point the sdk at the local tarball so
|
|
60
|
+
// the install copies our freshly-built sdk into node_modules.
|
|
61
|
+
const pkgPath = path.join(deck, 'package.json');
|
|
62
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
63
|
+
rewriteKitPackageJson(pkg, kit, PUBLISHED_REFS);
|
|
64
|
+
pkg.dependencies['castle-web-sdk'] = `file:${toPosixPath(sdkTarball)}`;
|
|
65
|
+
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
|
|
66
|
+
// Deck deps are pure JS (no native binaries), so this mac-built
|
|
67
|
+
// node_modules runs as-is on linux/e2b.
|
|
68
|
+
console.log(`Installing deps for ${kit}...`);
|
|
69
|
+
execFileSync('npm', ['install', '--no-audit', '--no-fund', '--loglevel=error'], {
|
|
70
|
+
cwd: deck,
|
|
71
|
+
stdio: 'inherit',
|
|
72
|
+
});
|
|
73
|
+
// Restore the published sdk range (node_modules already holds the real
|
|
74
|
+
// sdk) and drop the lock pinning the local tarball path.
|
|
75
|
+
pkg.dependencies['castle-web-sdk'] = `^${PUBLISHED_SDK_VERSION}`;
|
|
76
|
+
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
|
|
77
|
+
fs.rmSync(path.join(deck, 'package-lock.json'), { force: true });
|
|
78
|
+
const archivePath = getKitArchivePath(kit);
|
|
79
|
+
fs.mkdirSync(path.dirname(archivePath), { recursive: true });
|
|
80
|
+
fs.rmSync(archivePath, { force: true });
|
|
81
|
+
console.log(`Archiving ${kit} -> ${archivePath}`);
|
|
82
|
+
execFileSync('tar', ['-czf', archivePath, '-C', deck, '.'], { stdio: 'inherit' });
|
|
83
|
+
const sizeMb = (fs.statSync(archivePath).size / 1e6).toFixed(1);
|
|
84
|
+
console.log(` ${kit} archive: ${sizeMb} MB`);
|
|
85
|
+
}
|
|
86
|
+
finally {
|
|
87
|
+
fs.rmSync(stagingRoot, { recursive: true, force: true });
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
// Generate archives for the given kits (default: every kit under kits/). Builds
|
|
91
|
+
// + packs the sdk once, then archives each kit against that same tarball.
|
|
92
|
+
export function buildKitArchives(kits) {
|
|
93
|
+
const kitsDir = getKitsDir();
|
|
94
|
+
const targets = kits ?? discoverKits(kitsDir);
|
|
95
|
+
if (targets.length === 0) {
|
|
96
|
+
console.error(`No kits found under ${kitsDir}.`);
|
|
97
|
+
process.exit(1);
|
|
98
|
+
}
|
|
99
|
+
const sdkTarball = packSdk();
|
|
100
|
+
try {
|
|
101
|
+
for (const kit of targets)
|
|
102
|
+
buildKitArchive(kit, sdkTarball);
|
|
103
|
+
}
|
|
104
|
+
finally {
|
|
105
|
+
fs.rmSync(path.dirname(sdkTarball), { recursive: true, force: true });
|
|
106
|
+
}
|
|
107
|
+
console.log(`Done. Archived: ${targets.join(', ')}`);
|
|
108
|
+
}
|
package/dist/init.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { execSync } from
|
|
2
|
-
import * as fs from
|
|
3
|
-
import * as path from
|
|
4
|
-
import { COMMON_INSTRUCTIONS } from
|
|
5
|
-
import { getCliEntryPath, getKitsDir, getRepoRoot, getSdkPackagePath, toPosixPath } from
|
|
6
|
-
import { serve } from
|
|
1
|
+
import { execSync } from "child_process";
|
|
2
|
+
import * as fs from "fs";
|
|
3
|
+
import * as path from "path";
|
|
4
|
+
import { COMMON_INSTRUCTIONS } from "./commonInstructions.js";
|
|
5
|
+
import { getCliEntryPath, getKitsDir, getRepoRoot, getSdkPackagePath, toPosixPath, } from "./localPaths.js";
|
|
6
|
+
import { serve } from "./serve.js";
|
|
7
7
|
const INDEX_HTML = `<!DOCTYPE html>
|
|
8
8
|
<html>
|
|
9
9
|
<head>
|
|
@@ -31,14 +31,20 @@ card.appendChild(el);
|
|
|
31
31
|
`;
|
|
32
32
|
// Default kit copied by `init` when no --kit is given. `none`/`bare` skip the
|
|
33
33
|
// kit and produce the minimal index.html + game.js stub above.
|
|
34
|
-
const DEFAULT_KIT =
|
|
34
|
+
const DEFAULT_KIT = "basic-2d";
|
|
35
35
|
// Registry version of castle-web-sdk to inject when scaffolding from a
|
|
36
36
|
// globally-installed castle-web (not from inside the workspace). Bumped
|
|
37
37
|
// alongside cli/sdk version bumps.
|
|
38
|
-
const PUBLISHED_SDK_VERSION =
|
|
38
|
+
const PUBLISHED_SDK_VERSION = "0.4.4";
|
|
39
39
|
// Never copied into a fresh deck: build/dependency junk, and castle.json (a
|
|
40
40
|
// fresh deck has no deckId until its first save-deck).
|
|
41
|
-
const KIT_COPY_EXCLUDE = new Set([
|
|
41
|
+
const KIT_COPY_EXCLUDE = new Set([
|
|
42
|
+
"node_modules",
|
|
43
|
+
".castle",
|
|
44
|
+
"dist",
|
|
45
|
+
".git",
|
|
46
|
+
"castle.json",
|
|
47
|
+
]);
|
|
42
48
|
// Resolve how a scaffolded deck should reference the sdk + cli. Both the bare
|
|
43
49
|
// and kit scaffold paths go through here so they stay in sync.
|
|
44
50
|
// workspace mode (sdk/ sits next to cli/, i.e. running from a checkout):
|
|
@@ -52,11 +58,17 @@ function resolveScaffoldRefs() {
|
|
|
52
58
|
// npm package and need the published refs + `castle-web` binary.
|
|
53
59
|
const workspaceMode = fs.existsSync(sdkPath);
|
|
54
60
|
const sdkPathPosix = workspaceMode ? toPosixPath(sdkPath) : null;
|
|
55
|
-
const cliDistAbs = workspaceMode
|
|
61
|
+
const cliDistAbs = workspaceMode
|
|
62
|
+
? toPosixPath(path.dirname(getCliEntryPath()))
|
|
63
|
+
: null;
|
|
56
64
|
return {
|
|
57
65
|
workspaceMode,
|
|
58
|
-
sdkRef: workspaceMode
|
|
59
|
-
|
|
66
|
+
sdkRef: workspaceMode
|
|
67
|
+
? `file:${sdkPathPosix}`
|
|
68
|
+
: `^${PUBLISHED_SDK_VERSION}`,
|
|
69
|
+
cliCommand: workspaceMode
|
|
70
|
+
? `node ${toPosixPath(getCliEntryPath())}`
|
|
71
|
+
: "castle-web",
|
|
60
72
|
cliDistAbs,
|
|
61
73
|
sdkPathPosix,
|
|
62
74
|
};
|
|
@@ -66,9 +78,9 @@ function makeClaudeMd() {
|
|
|
66
78
|
// castle-experimental-web checkout, but breaks when the scaffold lives
|
|
67
79
|
// outside the repo (the relative path no longer resolves).
|
|
68
80
|
const repoRoot = getRepoRoot();
|
|
69
|
-
const upstream = path.join(repoRoot,
|
|
81
|
+
const upstream = path.join(repoRoot, "CLAUDE.md");
|
|
70
82
|
try {
|
|
71
|
-
return fs.readFileSync(upstream,
|
|
83
|
+
return fs.readFileSync(upstream, "utf8").trimEnd() + "\n";
|
|
72
84
|
}
|
|
73
85
|
catch {
|
|
74
86
|
return `# Castle Experimental Web\n\nSee https://github.com/castle-xyz/castle-experimental-web for the agent guide.\n`;
|
|
@@ -78,13 +90,15 @@ function makeClaudeMd() {
|
|
|
78
90
|
// the kit's (or bare) CLAUDE.md is written; the AGENTS.md symlink picks the
|
|
79
91
|
// appended content up for free.
|
|
80
92
|
function appendCommonInstructions(projectDir) {
|
|
81
|
-
const claudePath = path.join(projectDir,
|
|
82
|
-
const existing = fs.existsSync(claudePath)
|
|
93
|
+
const claudePath = path.join(projectDir, "CLAUDE.md");
|
|
94
|
+
const existing = fs.existsSync(claudePath)
|
|
95
|
+
? fs.readFileSync(claudePath, "utf8").trimEnd() + "\n\n"
|
|
96
|
+
: "";
|
|
83
97
|
fs.writeFileSync(claudePath, existing + COMMON_INSTRUCTIONS);
|
|
84
98
|
}
|
|
85
99
|
function tryMakeAgentsSymlink(agentsPath) {
|
|
86
100
|
try {
|
|
87
|
-
fs.symlinkSync(
|
|
101
|
+
fs.symlinkSync("CLAUDE.md", agentsPath);
|
|
88
102
|
}
|
|
89
103
|
catch {
|
|
90
104
|
// symlink already exists / unsupported FS — non-fatal
|
|
@@ -95,37 +109,37 @@ function makePackageJson(projectDir) {
|
|
|
95
109
|
return {
|
|
96
110
|
name: path.basename(projectDir),
|
|
97
111
|
private: true,
|
|
98
|
-
type:
|
|
112
|
+
type: "module",
|
|
99
113
|
scripts: {
|
|
100
114
|
restart: `${cliCommand} restart .`,
|
|
101
115
|
screenshot: `${cliCommand} screenshot .`,
|
|
102
|
-
|
|
116
|
+
"save-deck": `${cliCommand} save-deck .`,
|
|
103
117
|
},
|
|
104
118
|
dependencies: {
|
|
105
|
-
|
|
119
|
+
"castle-web-sdk": sdkRef,
|
|
106
120
|
},
|
|
107
121
|
};
|
|
108
122
|
}
|
|
109
123
|
// Some coding agents read AGENTS.md by convention. Symlink so they get the
|
|
110
124
|
// same guidance without a duplicate copy.
|
|
111
125
|
function ensureAgentsSymlink(projectDir) {
|
|
112
|
-
const agentsPath = path.join(projectDir,
|
|
126
|
+
const agentsPath = path.join(projectDir, "AGENTS.md");
|
|
113
127
|
if (fs.lstatSync(agentsPath, { throwIfNoEntry: false }))
|
|
114
128
|
return;
|
|
115
129
|
// Don't create a dangling link — only symlink when CLAUDE.md is present.
|
|
116
|
-
if (!fs.existsSync(path.join(projectDir,
|
|
130
|
+
if (!fs.existsSync(path.join(projectDir, "CLAUDE.md")))
|
|
117
131
|
return;
|
|
118
132
|
tryMakeAgentsSymlink(agentsPath);
|
|
119
133
|
}
|
|
120
134
|
// Bare scaffold: a plain code-only deck with no kit framework.
|
|
121
135
|
function scaffoldBare(projectDir) {
|
|
122
136
|
fs.mkdirSync(projectDir, { recursive: true });
|
|
123
|
-
fs.writeFileSync(path.join(projectDir,
|
|
124
|
-
fs.writeFileSync(path.join(projectDir,
|
|
125
|
-
fs.writeFileSync(path.join(projectDir,
|
|
137
|
+
fs.writeFileSync(path.join(projectDir, "index.html"), INDEX_HTML);
|
|
138
|
+
fs.writeFileSync(path.join(projectDir, "game.js"), GAME_JS);
|
|
139
|
+
fs.writeFileSync(path.join(projectDir, "CLAUDE.md"), makeClaudeMd());
|
|
126
140
|
appendCommonInstructions(projectDir);
|
|
127
141
|
ensureAgentsSymlink(projectDir);
|
|
128
|
-
fs.writeFileSync(path.join(projectDir,
|
|
142
|
+
fs.writeFileSync(path.join(projectDir, "package.json"), JSON.stringify(makePackageJson(projectDir), null, 2) + "\n");
|
|
129
143
|
}
|
|
130
144
|
// Copy a framework kit from kits/<kit>/ into the new deck dir, dropping
|
|
131
145
|
// build/dependency junk and castle.json.
|
|
@@ -133,7 +147,7 @@ function scaffoldFromKit(kit, projectDir) {
|
|
|
133
147
|
const kitDir = path.join(getKitsDir(), kit);
|
|
134
148
|
if (!fs.existsSync(kitDir) || !fs.statSync(kitDir).isDirectory()) {
|
|
135
149
|
console.error(`Kit "${kit}" not found at ${kitDir}.`);
|
|
136
|
-
console.error(
|
|
150
|
+
console.error("Available kits:");
|
|
137
151
|
try {
|
|
138
152
|
const kits = fs
|
|
139
153
|
.readdirSync(getKitsDir())
|
|
@@ -142,12 +156,12 @@ function scaffoldFromKit(kit, projectDir) {
|
|
|
142
156
|
for (const name of kits)
|
|
143
157
|
console.error(` ${name}`);
|
|
144
158
|
else
|
|
145
|
-
console.error(
|
|
159
|
+
console.error(" (none)");
|
|
146
160
|
}
|
|
147
161
|
catch {
|
|
148
|
-
console.error(
|
|
162
|
+
console.error(" (none — kits/ directory is missing)");
|
|
149
163
|
}
|
|
150
|
-
console.error(
|
|
164
|
+
console.error("Or use `--kit none` for a bare code-only deck.");
|
|
151
165
|
process.exit(1);
|
|
152
166
|
}
|
|
153
167
|
fs.cpSync(kitDir, projectDir, {
|
|
@@ -161,10 +175,10 @@ function scaffoldFromKit(kit, projectDir) {
|
|
|
161
175
|
// deck lives at castle-experimental-web/decks/<name>/. Rewrite both to
|
|
162
176
|
// absolute paths so the scaffolded deck works anywhere -- including under
|
|
163
177
|
// /tmp where macOS's /tmp -> /private/tmp symlink breaks relative-path math.
|
|
164
|
-
const pkgPath = path.join(projectDir,
|
|
178
|
+
const pkgPath = path.join(projectDir, "package.json");
|
|
165
179
|
if (fs.existsSync(pkgPath)) {
|
|
166
180
|
try {
|
|
167
|
-
const pkg = JSON.parse(fs.readFileSync(pkgPath,
|
|
181
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
|
|
168
182
|
pkg.name = path.basename(projectDir);
|
|
169
183
|
// Local-dev paths (`file:../../sdk` / `node ../../cli/dist/index.js`) only
|
|
170
184
|
// work when the deck lives inside the castle-experimental-web workspace.
|
|
@@ -173,13 +187,13 @@ function scaffoldFromKit(kit, projectDir) {
|
|
|
173
187
|
// the bare scaffold path uses.
|
|
174
188
|
const { workspaceMode, sdkRef, cliDistAbs, sdkPathPosix } = resolveScaffoldRefs();
|
|
175
189
|
if (pkg.dependencies &&
|
|
176
|
-
typeof pkg.dependencies[
|
|
177
|
-
pkg.dependencies[
|
|
178
|
-
pkg.dependencies[
|
|
190
|
+
typeof pkg.dependencies["castle-web-sdk"] === "string" &&
|
|
191
|
+
pkg.dependencies["castle-web-sdk"].startsWith("file:")) {
|
|
192
|
+
pkg.dependencies["castle-web-sdk"] = sdkRef;
|
|
179
193
|
}
|
|
180
194
|
if (pkg.scripts) {
|
|
181
195
|
for (const k of Object.keys(pkg.scripts)) {
|
|
182
|
-
if (typeof pkg.scripts[k] !==
|
|
196
|
+
if (typeof pkg.scripts[k] !== "string")
|
|
183
197
|
continue;
|
|
184
198
|
if (workspaceMode) {
|
|
185
199
|
pkg.scripts[k] = pkg.scripts[k]
|
|
@@ -189,13 +203,13 @@ function scaffoldFromKit(kit, projectDir) {
|
|
|
189
203
|
else {
|
|
190
204
|
// Globally-installed: route through the `castle-web` binary on PATH.
|
|
191
205
|
pkg.scripts[k] = pkg.scripts[k]
|
|
192
|
-
.replace(/node\s+\.\.\/\.\.\/cli\/dist\/index\.js/g,
|
|
206
|
+
.replace(/node\s+\.\.\/\.\.\/cli\/dist\/index\.js/g, "castle-web")
|
|
193
207
|
.replace(/await import\((['"])\.\.\/\.\.\/cli\/dist\/bundle\.js\1\)/g, "await import('castle-web-cli/dist/bundle.js')")
|
|
194
|
-
.replace(/\.\.\/\.\.\/sdk/g,
|
|
208
|
+
.replace(/\.\.\/\.\.\/sdk/g, "");
|
|
195
209
|
}
|
|
196
210
|
}
|
|
197
211
|
}
|
|
198
|
-
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) +
|
|
212
|
+
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
199
213
|
}
|
|
200
214
|
catch {
|
|
201
215
|
// kit shipped an unparseable package.json — leave it for the user to fix
|
|
@@ -203,13 +217,43 @@ function scaffoldFromKit(kit, projectDir) {
|
|
|
203
217
|
}
|
|
204
218
|
// Every deck needs a CLAUDE.md so coding agents know how castle-web works.
|
|
205
219
|
// Keep the kit's own if it ships one; otherwise generate from the upstream.
|
|
206
|
-
const claudePath = path.join(projectDir,
|
|
220
|
+
const claudePath = path.join(projectDir, "CLAUDE.md");
|
|
207
221
|
if (!fs.existsSync(claudePath)) {
|
|
208
222
|
fs.writeFileSync(claudePath, makeClaudeMd());
|
|
209
223
|
}
|
|
210
224
|
appendCommonInstructions(projectDir);
|
|
211
225
|
ensureAgentsSymlink(projectDir);
|
|
212
226
|
}
|
|
227
|
+
function hasPnpm() {
|
|
228
|
+
try {
|
|
229
|
+
execSync("pnpm --version", { stdio: "ignore" });
|
|
230
|
+
return true;
|
|
231
|
+
}
|
|
232
|
+
catch {
|
|
233
|
+
return false;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
// Install the scaffolded deck's deps. Prefer pnpm -- in the e2b template a pnpm
|
|
237
|
+
// store is baked in, so `pnpm install` is near-instant (hardlinks from the
|
|
238
|
+
// store, no download). Fall back to npm when pnpm isn't on PATH (e.g. a local
|
|
239
|
+
// laptop that never installed it). --prefer-offline uses the store/cache first
|
|
240
|
+
// and only hits the network for anything missing.
|
|
241
|
+
function installDeps(projectDir) {
|
|
242
|
+
if (hasPnpm()) {
|
|
243
|
+
console.log("Installing deps (pnpm)...");
|
|
244
|
+
execSync("pnpm install --prefer-offline", {
|
|
245
|
+
cwd: projectDir,
|
|
246
|
+
stdio: "inherit",
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
else {
|
|
250
|
+
console.log("Installing deps (npm)...");
|
|
251
|
+
execSync("npm install --no-audit --no-fund --loglevel=error", {
|
|
252
|
+
cwd: projectDir,
|
|
253
|
+
stdio: "inherit",
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
}
|
|
213
257
|
export async function init(dir, opts = {}) {
|
|
214
258
|
const projectDir = path.resolve(dir);
|
|
215
259
|
if (fs.existsSync(projectDir) && fs.readdirSync(projectDir).length > 0) {
|
|
@@ -217,45 +261,44 @@ export async function init(dir, opts = {}) {
|
|
|
217
261
|
process.exit(1);
|
|
218
262
|
}
|
|
219
263
|
const kit = opts.kit ?? DEFAULT_KIT;
|
|
220
|
-
const bare = kit ===
|
|
264
|
+
const bare = kit === "none" || kit === "bare";
|
|
221
265
|
if (bare) {
|
|
222
266
|
scaffoldBare(projectDir);
|
|
223
267
|
}
|
|
224
268
|
else {
|
|
225
269
|
scaffoldFromKit(kit, projectDir);
|
|
226
270
|
}
|
|
227
|
-
console.log(`Created project in ${projectDir}/${bare ?
|
|
228
|
-
//
|
|
229
|
-
//
|
|
271
|
+
console.log(`Created project in ${projectDir}/${bare ? "" : ` (from kit "${kit}")`}`);
|
|
272
|
+
// Always install deps so the deck is ready to serve/edit immediately. The
|
|
273
|
+
// serve step is what `--no-serve` skips (callers like the cloud launcher run
|
|
274
|
+
// their own serve), but they still want the deps in place.
|
|
275
|
+
console.log("");
|
|
276
|
+
let installed = false;
|
|
277
|
+
try {
|
|
278
|
+
installDeps(projectDir);
|
|
279
|
+
installed = true;
|
|
280
|
+
}
|
|
281
|
+
catch {
|
|
282
|
+
console.error("dependency install failed; re-run `pnpm install` (or `npm install`) in the deck.");
|
|
283
|
+
}
|
|
230
284
|
const autoServe = opts.serve !== false;
|
|
231
|
-
if (autoServe) {
|
|
232
|
-
console.log('');
|
|
233
|
-
console.log('Installing deps + serving (pass --no-serve to skip)...');
|
|
234
|
-
try {
|
|
235
|
-
execSync('npm install --no-audit --no-fund --loglevel=error', {
|
|
236
|
-
cwd: projectDir,
|
|
237
|
-
stdio: 'inherit',
|
|
238
|
-
});
|
|
239
|
-
}
|
|
240
|
-
catch {
|
|
241
|
-
console.error('npm install failed; skipping serve. Re-run yourself with `npm install && castle-web serve .` (& in your shell to background it).');
|
|
242
|
-
return;
|
|
243
|
-
}
|
|
285
|
+
if (autoServe && installed) {
|
|
244
286
|
// Call serve() with detach so init returns once the server is up. serve()
|
|
245
287
|
// handles the background spawn internally; init doesn't shell out.
|
|
246
288
|
// Bind all interfaces by default so a tailnet / LAN browser can reach
|
|
247
289
|
// the served page; users can override host on a subsequent serve call.
|
|
248
290
|
// Open in the user's default browser unless we're clearly headless (SSH
|
|
249
291
|
// session) or the user has opted out via CASTLE_WEB_CLI_NO_OPEN=1.
|
|
250
|
-
const noOpen = process.env.CASTLE_WEB_CLI_NO_OPEN ===
|
|
292
|
+
const noOpen = process.env.CASTLE_WEB_CLI_NO_OPEN === "1" ||
|
|
251
293
|
!!process.env.SSH_CONNECTION ||
|
|
252
294
|
!!process.env.SSH_TTY;
|
|
253
|
-
await serve(projectDir, { host:
|
|
295
|
+
await serve(projectDir, { host: "0.0.0.0", detach: true, open: !noOpen });
|
|
254
296
|
return;
|
|
255
297
|
}
|
|
256
|
-
console.log(
|
|
257
|
-
console.log(
|
|
298
|
+
console.log("");
|
|
299
|
+
console.log("Next steps:");
|
|
258
300
|
console.log(` cd ${dir}`);
|
|
259
|
-
|
|
260
|
-
|
|
301
|
+
if (!installed)
|
|
302
|
+
console.log(" pnpm install # or: npm install");
|
|
303
|
+
console.log(" castle-web serve . # & in your shell to background it");
|
|
261
304
|
}
|