castle-web-cli 0.4.114 → 0.4.116
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/agent-prompts.js +2 -2
- package/dist/agent.d.ts +9 -9
- package/dist/agent.js +590 -589
- package/dist/castleJson.d.ts +7 -0
- package/dist/castleJson.js +10 -0
- package/dist/editorConfig.d.ts +34 -0
- package/dist/editorConfig.js +125 -0
- package/dist/ide.js +245 -120
- package/dist/imports.d.ts +10 -0
- package/dist/imports.js +149 -17
- package/dist/init.d.ts +3 -0
- package/dist/init.js +113 -88
- package/dist/install.d.ts +1 -1
- package/dist/install.js +26 -24
- package/dist/serve.js +16 -0
- package/dist/shell/assets/index-DiPlPGyg.js +144 -0
- package/dist/shell/assets/index-Y6cJRRCX.css +1 -0
- package/dist/shell/index.html +2 -2
- package/dist/unsupportedMedia.d.ts +1 -0
- package/dist/unsupportedMedia.js +54 -0
- package/dist/vitePlugins.js +13 -20
- package/kits/basic-2d/castle.json +1 -1
- package/kits/basic-2d/editors/behaviorRegistry.js +5 -7
- package/kits/physics-2d/CLAUDE.md +65 -34
- package/kits/physics-2d/{physics/behaviors → behaviors}/AnalogStick.jsx +3 -3
- package/kits/physics-2d/behaviors/Collider.jsx +1 -1
- package/kits/physics-2d/{physics/behaviors → behaviors}/Draggable.jsx +3 -3
- package/kits/physics-2d/{physics/behaviors → behaviors}/Joints.jsx +5 -5
- package/kits/physics-2d/{physics/behaviors → behaviors}/RigidBody.jsx +2 -2
- package/kits/physics-2d/{physics/behaviors → behaviors}/Slingshot.jsx +3 -3
- package/kits/physics-2d/behaviors/Sound.jsx +152 -0
- package/kits/physics-2d/behaviors/Sprite.jsx +112 -42
- package/kits/physics-2d/behaviors/Tone.jsx +83 -0
- package/kits/physics-2d/behaviors/Video.jsx +111 -0
- package/kits/physics-2d/behaviors/tint.js +24 -9
- package/kits/physics-2d/blueprints/ball.scene +1 -1
- package/kits/physics-2d/blueprints/block.scene +1 -1
- package/kits/physics-2d/blueprints/cauldron.scene +1 -1
- package/kits/physics-2d/blueprints/crate.scene +1 -1
- package/kits/physics-2d/castle.json +74 -4
- package/kits/physics-2d/docs/pxart-format.md +537 -51
- package/kits/physics-2d/editors/BlueprintLibrary.jsx +7 -3
- package/kits/physics-2d/editors/ImageViewer.jsx +206 -0
- package/kits/physics-2d/editors/MediaPlayer.jsx +57 -0
- package/kits/physics-2d/editors/PxArtEditor.jsx +1794 -65
- package/kits/physics-2d/editors/SceneEditor.jsx +7 -3
- package/kits/physics-2d/editors/SingleEditor.jsx +8 -0
- package/kits/physics-2d/editors/behaviorRegistry.js +5 -7
- package/kits/physics-2d/editors/brushFit.js +535 -0
- package/kits/physics-2d/editors/brushShapes.js +140 -0
- package/kits/physics-2d/editors/mediaFile.js +31 -0
- package/kits/physics-2d/editors/mediaViewer.module.css +128 -0
- package/kits/physics-2d/editors/pathOverlay.js +340 -0
- package/kits/physics-2d/editors/pathTools.js +1906 -0
- package/kits/physics-2d/editors/pixelCanvas.js +13 -0
- package/kits/physics-2d/editors/pixelEditorChrome.jsx +2 -2
- package/kits/physics-2d/editors/pixelGeometry.js +4 -2
- package/kits/physics-2d/editors/pixelInspector.jsx +410 -37
- package/kits/physics-2d/editors/pxArtEditorModel.js +172 -16
- package/kits/physics-2d/editors/pxArtTimeline.jsx +163 -43
- package/kits/physics-2d/editors/pxArtTimeline.module.css +31 -5
- package/kits/physics-2d/engine/ScenePlayer.jsx +1 -0
- package/kits/physics-2d/engine/art.js +105 -0
- package/kits/physics-2d/engine/assets.js +12 -4
- package/kits/physics-2d/engine/audioContext.js +34 -0
- package/kits/physics-2d/engine/blueprint.js +3 -3
- package/kits/physics-2d/engine/collider.js +28 -17
- package/kits/physics-2d/engine/files.js +77 -1
- package/kits/physics-2d/engine/liveReload.js +1 -1
- package/kits/physics-2d/engine/media.js +212 -0
- package/kits/physics-2d/{physics → engine/physics}/PhysicsSystem.js +1 -1
- package/kits/physics-2d/{physics → engine/physics}/jointArt.js +3 -4
- package/kits/physics-2d/{physics → engine/physics}/matterBridge.js +2 -9
- package/kits/physics-2d/engine/pxart.js +153 -35
- package/kits/physics-2d/engine/pxartPath.js +1356 -0
- package/kits/physics-2d/engine/pxartSmooth.js +276 -125
- package/kits/physics-2d/engine/scene.js +12 -0
- package/kits/physics-2d/engine/tone.js +112 -0
- package/kits/physics-2d/engine/ui.jsx +22 -1
- package/kits/physics-2d/engine/ui.module.css +36 -12
- package/kits/physics-2d/package-lock.json +1 -1
- package/kits/physics-2d/scripts/draw.mjs +7 -7
- package/kits/physics-2d/scripts/import-svg.mjs +1231 -0
- package/kits/physics-2d/scripts/svg-emission-guide.md +92 -0
- package/kits/physics-2d/systems/media.js +34 -0
- package/kits/physics-2d/systems/physics.js +12 -3
- package/package.json +1 -1
- package/dist/shell/assets/index-CqpY1xZR.js +0 -144
- package/dist/shell/assets/index-DCwVFL5u.css +0 -1
- package/kits/physics-2d/physics/index.js +0 -26
- /package/kits/physics-2d/drawings/{block.pxart → block.sprite} +0 -0
- /package/kits/physics-2d/drawings/{cauldron.pxart → cauldron.sprite} +0 -0
- /package/kits/physics-2d/drawings/{joint-rope.pxart → joint-rope.sprite} +0 -0
- /package/kits/physics-2d/{physics → engine/physics}/controls.js +0 -0
- /package/kits/physics-2d/{physics → engine/physics}/joints.js +0 -0
package/dist/imports.js
CHANGED
|
@@ -5,7 +5,7 @@ import * as api from './api.js';
|
|
|
5
5
|
import { runTar } from './save-deck.js';
|
|
6
6
|
import { normalizeDeckPackageJson } from './normalize.js';
|
|
7
7
|
import { getKitsDir } from './localPaths.js';
|
|
8
|
-
import { readCastleJsonOrThrow as readCastleJson, } from './castleJson.js';
|
|
8
|
+
import { readCastleJson as tryReadCastleJson, readCastleJsonOrThrow as readCastleJson, } from './castleJson.js';
|
|
9
9
|
// Adding another deck as a dependency (`castle-web add-import`; removing one is a
|
|
10
10
|
// later command). Deliberately NOT `get-deck`: that one
|
|
11
11
|
// replaces THIS deck's own source from the server (and carries guards for the
|
|
@@ -80,11 +80,7 @@ function slugifyAlias(name) {
|
|
|
80
80
|
// An explicit `--as` may already be qualified (`someone.their-deck`); slugify
|
|
81
81
|
// each part so the separator survives.
|
|
82
82
|
function slugifyGivenAlias(name) {
|
|
83
|
-
return name
|
|
84
|
-
.split('.')
|
|
85
|
-
.map(slugifyAlias)
|
|
86
|
-
.filter(Boolean)
|
|
87
|
-
.join('.');
|
|
83
|
+
return name.split('.').map(slugifyAlias).filter(Boolean).join('.');
|
|
88
84
|
}
|
|
89
85
|
// Aliases are qualified by author -- `lovefromtom.fish-town` -- because deck
|
|
90
86
|
// titles are not unique across users (nor even within one), and an import's name
|
|
@@ -255,7 +251,8 @@ export async function restoreMissingImports(deckDir) {
|
|
|
255
251
|
fs.cpSync(kitDir, destDir, {
|
|
256
252
|
recursive: true,
|
|
257
253
|
verbatimSymlinks: true,
|
|
258
|
-
filter: (src) => src === kitDir ||
|
|
254
|
+
filter: (src) => src === kitDir ||
|
|
255
|
+
!['node_modules', '.castle', 'dist', '.git'].includes(path.basename(src)),
|
|
259
256
|
});
|
|
260
257
|
}
|
|
261
258
|
else {
|
|
@@ -438,7 +435,9 @@ async function addTransitiveImports(deckDir, viaAlias, seen) {
|
|
|
438
435
|
// loop on a cycle.
|
|
439
436
|
export function importedDeckIds(deckDir) {
|
|
440
437
|
const pins = readCastleJson(deckDir)?.imports ?? {};
|
|
441
|
-
return new Set(Object.values(pins)
|
|
438
|
+
return new Set(Object.values(pins)
|
|
439
|
+
.map((p) => p?.deckId)
|
|
440
|
+
.filter((id) => !!id));
|
|
442
441
|
}
|
|
443
442
|
// The import itself. Throws ImportError rather than exiting, because this also
|
|
444
443
|
// runs inside the serve (from the editor's Import panel), where taking the
|
|
@@ -477,7 +476,52 @@ export async function addImportTo(dir, options = {}) {
|
|
|
477
476
|
const { replaced } = await placeImport(targetDir, alias, deckId, source);
|
|
478
477
|
const transitive = await addTransitiveImports(targetDir, alias, importedDeckIds(targetDir));
|
|
479
478
|
const dependencies = syncImportDependencies(targetDir);
|
|
480
|
-
|
|
479
|
+
// A kit declares its entry module (castle.json `main`). Importing one into a
|
|
480
|
+
// deck that is still the untouched `--kit none` scaffold means "start from this
|
|
481
|
+
// kit", so adopt its entry -- otherwise the deck keeps booting its own stub and
|
|
482
|
+
// the kit's runtime and editors never mount. A deck with any work in it is left
|
|
483
|
+
// alone and just told the option exists, since repointing it would go dark.
|
|
484
|
+
const importDir = path.join(targetDir, IMPORTS_DIR, alias);
|
|
485
|
+
const declaredMain = tryReadCastleJson(importDir)?.main;
|
|
486
|
+
let adoptedMain = null;
|
|
487
|
+
if (typeof declaredMain === 'string' && declaredMain.trim()) {
|
|
488
|
+
const { isPristineBareDeck, adoptImportEntry, writeStarterScene } = await import('./init.js');
|
|
489
|
+
if (isPristineBareDeck(targetDir)) {
|
|
490
|
+
adoptImportEntry(targetDir, alias, declaredMain.trim());
|
|
491
|
+
adoptedMain = declaredMain.trim();
|
|
492
|
+
// A kit's runtime with no scene to run is a blank screen, and the agent
|
|
493
|
+
// has nothing to edit. Give the deck the kit's own starter, same as
|
|
494
|
+
// `init --kit`. Only when the deck has no scenes -- never overwrite.
|
|
495
|
+
if (!fs.existsSync(path.join(targetDir, 'scenes'))) {
|
|
496
|
+
try {
|
|
497
|
+
writeStarterScene(targetDir, importDir, alias);
|
|
498
|
+
}
|
|
499
|
+
catch (e) {
|
|
500
|
+
console.warn(`Adopted ${alias} but could not write its starter scene: ${e instanceof Error ? e.message : String(e)}`);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
// An import's packages have to be ON DISK, not just named in package.json:
|
|
506
|
+
// the deck fails to load the moment its code imports one. Telling the caller
|
|
507
|
+
// to run `castle-web install` only worked for the CLI -- an import taken from
|
|
508
|
+
// the editor left the deck broken with no visible reason why.
|
|
509
|
+
if (dependencies.length > 0) {
|
|
510
|
+
try {
|
|
511
|
+
// Imported here rather than at the top: install.ts imports THIS module,
|
|
512
|
+
// and a top-level import back would be a cycle.
|
|
513
|
+
const { installDeps } = await import('./install.js');
|
|
514
|
+
await installDeps(targetDir, false);
|
|
515
|
+
}
|
|
516
|
+
catch (e) {
|
|
517
|
+
// Never fail the import over this: the files are already in place, and a
|
|
518
|
+
// hand-run install still fixes it.
|
|
519
|
+
console.warn(`Imported ${alias}, but installing its dependencies failed -- run \`castle-web install\`. ${e instanceof Error ? e.message : String(e)}`);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
const trimmedMain = typeof declaredMain === 'string' && declaredMain.trim() ? declaredMain.trim() : null;
|
|
523
|
+
const offersMain = trimmedMain && !adoptedMain ? trimmedMain : null;
|
|
524
|
+
return { deckId, alias, replaced, transitive, dependencies, adoptedMain, offersMain };
|
|
481
525
|
}
|
|
482
526
|
export async function addImport(dir, options = {}) {
|
|
483
527
|
let result;
|
|
@@ -500,8 +544,101 @@ export async function addImport(dir, options = {}) {
|
|
|
500
544
|
}
|
|
501
545
|
for (const dep of result.dependencies)
|
|
502
546
|
console.log(`Added dependency ${dep}`);
|
|
503
|
-
if (result.
|
|
504
|
-
console.log(
|
|
547
|
+
if (result.adoptedMain) {
|
|
548
|
+
console.log(`This deck now runs ${result.alias} (index.html -> ${result.adoptedMain}).`);
|
|
549
|
+
}
|
|
550
|
+
else if (result.offersMain) {
|
|
551
|
+
console.log(`${result.alias} is a kit. To run this deck on it, point index.html at ` +
|
|
552
|
+
`/${IMPORTS_DIR}/${result.alias}/${result.offersMain}.`);
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
// Take one import's update: replace its files with the version the server has
|
|
556
|
+
// now, then follow that version's own pins for anything newly required. Both
|
|
557
|
+
// `update-import` and the serve-start auto update come through here, so there
|
|
558
|
+
// is one path that replaces an import's files rather than two that have to be
|
|
559
|
+
// kept in step. `log` is how the caller labels the lines it produces.
|
|
560
|
+
async function applyImportUpdate(deckDir, alias, update, source, log) {
|
|
561
|
+
await placeImport(deckDir, alias, update.deckId, source, update.via);
|
|
562
|
+
log(`Updated ${alias} (${update.from} -> ${source.updatedAt})`);
|
|
563
|
+
// Its own dependencies may have moved too.
|
|
564
|
+
const transitive = await addTransitiveImports(deckDir, alias, importedDeckIds(deckDir));
|
|
565
|
+
for (const t of transitive)
|
|
566
|
+
log(`Also imported ${IMPORTS_DIR}/${t} (needed by ${alias})`);
|
|
567
|
+
}
|
|
568
|
+
// Whether the deck behind an import asked to be tracked rather than pinned --
|
|
569
|
+
// `autoUpdateWhenImported` in ITS castle.json, which is a statement its author
|
|
570
|
+
// makes about their own deck (see castleJson.ts).
|
|
571
|
+
//
|
|
572
|
+
// Read from the copy already on disk, NOT from the version about to be fetched.
|
|
573
|
+
// The flag the importer has is the one that was there when they took this
|
|
574
|
+
// version; letting the incoming version answer would let any deck start pushing
|
|
575
|
+
// to everyone who already imported it, after the fact. The cost is a one-update
|
|
576
|
+
// lag both ways -- turning the flag on reaches an existing importer only once
|
|
577
|
+
// they take an update by hand, and turning it off gets delivered by one last
|
|
578
|
+
// auto update -- which is the safe direction for both.
|
|
579
|
+
function importOptsIntoAutoUpdate(deckDir, alias) {
|
|
580
|
+
const config = tryReadCastleJson(path.join(deckDir, IMPORTS_DIR, alias));
|
|
581
|
+
return config?.autoUpdateWhenImported === true;
|
|
582
|
+
}
|
|
583
|
+
// Auto-update is honored only for imports of decks on the castle account. The
|
|
584
|
+
// flag is the mechanism and this is a leash on it: a deck that pushes new code
|
|
585
|
+
// to everyone who imported it is asking for a lot of trust, and until decks
|
|
586
|
+
// carry some other signal of it, the only ones that get it are the ones we
|
|
587
|
+
// publish (the framework kits -- the same account `init` qualifies their aliases
|
|
588
|
+
// with).
|
|
589
|
+
//
|
|
590
|
+
// Deliberately not a setting: nothing a deck or a person can flip, since that
|
|
591
|
+
// would be the leash undoing itself. The env var is a test seam -- the automated
|
|
592
|
+
// check can't publish a deck to the castle account (nothing should), so it
|
|
593
|
+
// substitutes which account counts and exercises both branches for real.
|
|
594
|
+
const AUTO_UPDATE_ACCOUNT = process.env.CASTLE_AUTO_UPDATE_TRUSTED_ACCOUNT ?? 'castle';
|
|
595
|
+
// Who published a deck. Readable without auth for the same decks webDeckSource
|
|
596
|
+
// is, so this answers in a sandbox with no login. A lookup that fails answers
|
|
597
|
+
// "not trusted": the failure direction has to be the one that leaves an import
|
|
598
|
+
// where its deck's author pinned it.
|
|
599
|
+
async function importIsOnTrustedAccount(deckId) {
|
|
600
|
+
const meta = await api.deckMeta(deckId).catch(() => null);
|
|
601
|
+
return meta?.creator?.username === AUTO_UPDATE_ACCOUNT;
|
|
602
|
+
}
|
|
603
|
+
// Move every opted-in import that the account gate allows to the version the
|
|
604
|
+
// server has now.
|
|
605
|
+
//
|
|
606
|
+
// Called once from serve start (see serve.ts) and nowhere else: that is the one
|
|
607
|
+
// moment where replacing a dependency's files can't pull the rug out from under
|
|
608
|
+
// anything -- no editor is holding unsaved state and no play session is running
|
|
609
|
+
// yet. A page reload happens mid-session, so it deliberately does not do this.
|
|
610
|
+
//
|
|
611
|
+
// Silent when nothing moves. Nothing here is fatal: an import that can't be
|
|
612
|
+
// checked (offline, no source on the server, a bad archive) stays exactly as it
|
|
613
|
+
// is pinned, and the serve starts anyway.
|
|
614
|
+
export async function autoUpdateImports(deckDir) {
|
|
615
|
+
const pins = tryReadCastleJson(deckDir)?.imports ?? {};
|
|
616
|
+
const updated = [];
|
|
617
|
+
for (const [alias, pin] of Object.entries(pins)) {
|
|
618
|
+
// A kit copied from the CLI has no deck to ask; it moves when the CLI does.
|
|
619
|
+
if (!pin?.deckId)
|
|
620
|
+
continue;
|
|
621
|
+
if (!importOptsIntoAutoUpdate(deckDir, alias))
|
|
622
|
+
continue;
|
|
623
|
+
try {
|
|
624
|
+
if (!(await importIsOnTrustedAccount(pin.deckId)))
|
|
625
|
+
continue;
|
|
626
|
+
const source = await api.webDeckSource(pin.deckId);
|
|
627
|
+
if (!source || source.updatedAt === pin.version)
|
|
628
|
+
continue;
|
|
629
|
+
await applyImportUpdate(deckDir, alias, { deckId: pin.deckId, from: pin.version, via: pin.via }, source, (message) => console.log(`auto-update: ${message}`));
|
|
630
|
+
updated.push({ alias, from: pin.version, to: source.updatedAt });
|
|
631
|
+
}
|
|
632
|
+
catch (e) {
|
|
633
|
+
console.warn(`auto-update: left "${alias}" as it is -- ${e instanceof Error ? e.message : String(e)}`);
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
if (updated.length > 0) {
|
|
637
|
+
for (const dep of syncImportDependencies(deckDir)) {
|
|
638
|
+
console.log(`auto-update: added dependency ${dep}`);
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
return updated;
|
|
505
642
|
}
|
|
506
643
|
// `castle-web update-import [alias] [dir]`: re-fetch an import (or all of them)
|
|
507
644
|
// at the version the server has now. Updates happen while editing, never on
|
|
@@ -552,12 +689,7 @@ export async function updateImport(dir, options = {}) {
|
|
|
552
689
|
changed = true;
|
|
553
690
|
continue;
|
|
554
691
|
}
|
|
555
|
-
await
|
|
556
|
-
console.log(`Updated ${alias} (${pin.version} -> ${source.updatedAt})`);
|
|
557
|
-
// Its own dependencies may have moved too.
|
|
558
|
-
const transitive = await addTransitiveImports(deckDir, alias, importedDeckIds(deckDir));
|
|
559
|
-
for (const t of transitive)
|
|
560
|
-
console.log(`Also imported ${IMPORTS_DIR}/${t} (needed by ${alias})`);
|
|
692
|
+
await applyImportUpdate(deckDir, alias, { deckId: pin.deckId, from: pin.version, via: pin.via }, source, (message) => console.log(message));
|
|
561
693
|
changed = true;
|
|
562
694
|
}
|
|
563
695
|
if (!changed)
|
package/dist/init.d.ts
CHANGED
|
@@ -5,7 +5,10 @@ export declare function resolveScaffoldRefs(): {
|
|
|
5
5
|
cliDistAbs: string | null;
|
|
6
6
|
sdkPathPosix: string | null;
|
|
7
7
|
};
|
|
8
|
+
export declare function isPristineBareDeck(dir: string): boolean;
|
|
9
|
+
export declare function adoptImportEntry(deckDir: string, alias: string, mainFile: string): void;
|
|
8
10
|
export declare function getCliVersion(): string;
|
|
11
|
+
export declare function writeStarterScene(projectDir: string, kitDir: string, alias: string): void;
|
|
9
12
|
export declare function init(dir: string, opts?: {
|
|
10
13
|
kit?: string;
|
|
11
14
|
serve?: boolean;
|
package/dist/init.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import * as fs from
|
|
2
|
-
import * as path from
|
|
3
|
-
import { COMMON_INSTRUCTIONS } from
|
|
4
|
-
import { installDeps } from
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import { COMMON_INSTRUCTIONS } from './commonInstructions.js';
|
|
4
|
+
import { installDeps } from './install.js';
|
|
5
|
+
import { deckMainFile, deckStarterScene } from './castleJson.js';
|
|
6
|
+
import { IMPORTS_DIR, lockImportTree } from './imports.js';
|
|
7
|
+
import { getCliEntryPath, getKitsDir, getRepoRoot, getSdkPackagePath, toPosixPath, } from './localPaths.js';
|
|
8
|
+
import { serve } from './serve.js';
|
|
8
9
|
const INDEX_HTML = `<!DOCTYPE html>
|
|
9
10
|
<html>
|
|
10
11
|
<head>
|
|
@@ -32,27 +33,22 @@ card.appendChild(el);
|
|
|
32
33
|
`;
|
|
33
34
|
// Default kit imported by `init` when no --kit is given. `none`/`bare` skip the
|
|
34
35
|
// kit and produce the minimal index.html + game.js stub above.
|
|
35
|
-
const DEFAULT_KIT =
|
|
36
|
+
const DEFAULT_KIT = 'physics-2d';
|
|
36
37
|
// Registry version of castle-web-sdk to inject when scaffolding from a
|
|
37
38
|
// globally-installed castle-web (not from inside the workspace). Bumped
|
|
38
39
|
// alongside cli/sdk version bumps.
|
|
39
|
-
const PUBLISHED_SDK_VERSION =
|
|
40
|
+
const PUBLISHED_SDK_VERSION = '0.4.12';
|
|
40
41
|
// The account the first-party kits are (to be) published under, so a kit
|
|
41
42
|
// imported from the CLI's copy is named the same as one fetched from the server.
|
|
42
|
-
const KIT_AUTHOR =
|
|
43
|
+
const KIT_AUTHOR = 'castle';
|
|
43
44
|
// Never copied into a deck's import: build/dependency junk.
|
|
44
|
-
const KIT_COPY_EXCLUDE = new Set([
|
|
45
|
-
"node_modules",
|
|
46
|
-
".castle",
|
|
47
|
-
"dist",
|
|
48
|
-
".git",
|
|
49
|
-
]);
|
|
45
|
+
const KIT_COPY_EXCLUDE = new Set(['node_modules', '.castle', 'dist', '.git']);
|
|
50
46
|
// The bare (`--kit none`) deck's default editor config: just the Files panel and
|
|
51
47
|
// a Play panel. No file filtering -- a bare deck is hand-rolled, so the author
|
|
52
48
|
// knows what's there; show everything for now.
|
|
53
49
|
const BARE_CASTLE_JSON = {
|
|
54
50
|
editor: {
|
|
55
|
-
initialPanels: [{ type:
|
|
51
|
+
initialPanels: [{ type: 'files' }, { type: 'playtest' }],
|
|
56
52
|
hiddenPaths: [],
|
|
57
53
|
visiblePaths: [],
|
|
58
54
|
},
|
|
@@ -70,17 +66,11 @@ export function resolveScaffoldRefs() {
|
|
|
70
66
|
// npm package and need the published refs + `castle-web` binary.
|
|
71
67
|
const workspaceMode = fs.existsSync(sdkPath);
|
|
72
68
|
const sdkPathPosix = workspaceMode ? toPosixPath(sdkPath) : null;
|
|
73
|
-
const cliDistAbs = workspaceMode
|
|
74
|
-
? toPosixPath(path.dirname(getCliEntryPath()))
|
|
75
|
-
: null;
|
|
69
|
+
const cliDistAbs = workspaceMode ? toPosixPath(path.dirname(getCliEntryPath())) : null;
|
|
76
70
|
return {
|
|
77
71
|
workspaceMode,
|
|
78
|
-
sdkRef: workspaceMode
|
|
79
|
-
|
|
80
|
-
: `^${PUBLISHED_SDK_VERSION}`,
|
|
81
|
-
cliCommand: workspaceMode
|
|
82
|
-
? `node ${toPosixPath(getCliEntryPath())}`
|
|
83
|
-
: "castle-web",
|
|
72
|
+
sdkRef: workspaceMode ? `file:${sdkPathPosix}` : `^${PUBLISHED_SDK_VERSION}`,
|
|
73
|
+
cliCommand: workspaceMode ? `node ${toPosixPath(getCliEntryPath())}` : 'castle-web',
|
|
84
74
|
cliDistAbs,
|
|
85
75
|
sdkPathPosix,
|
|
86
76
|
};
|
|
@@ -90,9 +80,9 @@ function makeClaudeMd() {
|
|
|
90
80
|
// castle-experimental-web checkout, but breaks when the scaffold lives
|
|
91
81
|
// outside the repo (the relative path no longer resolves).
|
|
92
82
|
const repoRoot = getRepoRoot();
|
|
93
|
-
const upstream = path.join(repoRoot,
|
|
83
|
+
const upstream = path.join(repoRoot, 'CLAUDE.md');
|
|
94
84
|
try {
|
|
95
|
-
return fs.readFileSync(upstream,
|
|
85
|
+
return fs.readFileSync(upstream, 'utf8').trimEnd() + '\n';
|
|
96
86
|
}
|
|
97
87
|
catch {
|
|
98
88
|
return `# Castle Experimental Web\n\nSee https://github.com/castle-xyz/castle-experimental-web for the agent guide.\n`;
|
|
@@ -102,15 +92,15 @@ function makeClaudeMd() {
|
|
|
102
92
|
// the kit's (or bare) CLAUDE.md is written; the AGENTS.md symlink picks the
|
|
103
93
|
// appended content up for free.
|
|
104
94
|
function appendCommonInstructions(projectDir) {
|
|
105
|
-
const claudePath = path.join(projectDir,
|
|
95
|
+
const claudePath = path.join(projectDir, 'CLAUDE.md');
|
|
106
96
|
const existing = fs.existsSync(claudePath)
|
|
107
|
-
? fs.readFileSync(claudePath,
|
|
108
|
-
:
|
|
97
|
+
? fs.readFileSync(claudePath, 'utf8').trimEnd() + '\n\n'
|
|
98
|
+
: '';
|
|
109
99
|
fs.writeFileSync(claudePath, existing + COMMON_INSTRUCTIONS);
|
|
110
100
|
}
|
|
111
101
|
function tryMakeAgentsSymlink(agentsPath) {
|
|
112
102
|
try {
|
|
113
|
-
fs.symlinkSync(
|
|
103
|
+
fs.symlinkSync('CLAUDE.md', agentsPath);
|
|
114
104
|
}
|
|
115
105
|
catch {
|
|
116
106
|
// symlink already exists / unsupported FS — non-fatal
|
|
@@ -124,11 +114,11 @@ function makeKitDeckScripts(kitDir, alias, cliCommand) {
|
|
|
124
114
|
const scripts = {
|
|
125
115
|
restart: `${cliCommand} restart .`,
|
|
126
116
|
screenshot: `${cliCommand} screenshot .`,
|
|
127
|
-
|
|
117
|
+
'save-deck': `${cliCommand} save-deck .`,
|
|
128
118
|
};
|
|
129
119
|
// `draw` is a kit script, not a CLI one, so it only exists if this kit ships
|
|
130
120
|
// it -- and it runs from the deck root, where the sprites belong.
|
|
131
|
-
if (fs.existsSync(path.join(kitDir,
|
|
121
|
+
if (fs.existsSync(path.join(kitDir, 'scripts', 'draw.mjs'))) {
|
|
132
122
|
scripts.draw = `node ${IMPORTS_DIR}/${alias}/scripts/draw.mjs`;
|
|
133
123
|
}
|
|
134
124
|
return scripts;
|
|
@@ -138,38 +128,59 @@ function makePackageJson(projectDir) {
|
|
|
138
128
|
return {
|
|
139
129
|
name: path.basename(projectDir),
|
|
140
130
|
private: true,
|
|
141
|
-
type:
|
|
131
|
+
type: 'module',
|
|
142
132
|
scripts: {
|
|
143
133
|
restart: `${cliCommand} restart .`,
|
|
144
134
|
screenshot: `${cliCommand} screenshot .`,
|
|
145
|
-
|
|
135
|
+
'save-deck': `${cliCommand} save-deck .`,
|
|
146
136
|
},
|
|
147
137
|
dependencies: {
|
|
148
|
-
|
|
138
|
+
'castle-web-sdk': sdkRef,
|
|
149
139
|
},
|
|
150
140
|
};
|
|
151
141
|
}
|
|
152
142
|
// Some coding agents read AGENTS.md by convention. Symlink so they get the
|
|
153
143
|
// same guidance without a duplicate copy.
|
|
154
144
|
function ensureAgentsSymlink(projectDir) {
|
|
155
|
-
const agentsPath = path.join(projectDir,
|
|
145
|
+
const agentsPath = path.join(projectDir, 'AGENTS.md');
|
|
156
146
|
if (fs.lstatSync(agentsPath, { throwIfNoEntry: false }))
|
|
157
147
|
return;
|
|
158
148
|
// Don't create a dangling link — only symlink when CLAUDE.md is present.
|
|
159
|
-
if (!fs.existsSync(path.join(projectDir,
|
|
149
|
+
if (!fs.existsSync(path.join(projectDir, 'CLAUDE.md')))
|
|
160
150
|
return;
|
|
161
151
|
tryMakeAgentsSymlink(agentsPath);
|
|
162
152
|
}
|
|
163
153
|
// Bare scaffold: a plain code-only deck with no kit framework.
|
|
154
|
+
// Whether a deck is still exactly what `--kit none` produced -- both the entry
|
|
155
|
+
// and the stub game it boots, untouched. `addImportTo` asks before repointing a
|
|
156
|
+
// deck's entry at an imported kit: on a pristine scaffold there is nothing to
|
|
157
|
+
// lose, and one edited character anywhere makes this false, so a deck someone
|
|
158
|
+
// has actually worked in is never quietly taken over.
|
|
159
|
+
export function isPristineBareDeck(dir) {
|
|
160
|
+
try {
|
|
161
|
+
return (fs.readFileSync(path.join(dir, 'index.html'), 'utf8') === INDEX_HTML &&
|
|
162
|
+
fs.readFileSync(path.join(dir, 'game.js'), 'utf8') === GAME_JS);
|
|
163
|
+
}
|
|
164
|
+
catch {
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
// Repoint a deck's entry at an imported deck's main module.
|
|
169
|
+
export function adoptImportEntry(deckDir, alias, mainFile) {
|
|
170
|
+
const file = path.join(deckDir, 'index.html');
|
|
171
|
+
const html = fs.readFileSync(file, 'utf8');
|
|
172
|
+
fs.writeFileSync(file, html.replace(/<script type="module" src="[^"]*"><\/script>/, `<script type="module" src="/${IMPORTS_DIR}/${alias}/${mainFile}"></script>`), 'utf8');
|
|
173
|
+
fs.rmSync(path.join(deckDir, 'game.js'), { force: true });
|
|
174
|
+
}
|
|
164
175
|
function scaffoldBare(projectDir) {
|
|
165
176
|
fs.mkdirSync(projectDir, { recursive: true });
|
|
166
|
-
fs.writeFileSync(path.join(projectDir,
|
|
167
|
-
fs.writeFileSync(path.join(projectDir,
|
|
168
|
-
fs.writeFileSync(path.join(projectDir,
|
|
169
|
-
fs.writeFileSync(path.join(projectDir,
|
|
177
|
+
fs.writeFileSync(path.join(projectDir, 'index.html'), INDEX_HTML);
|
|
178
|
+
fs.writeFileSync(path.join(projectDir, 'game.js'), GAME_JS);
|
|
179
|
+
fs.writeFileSync(path.join(projectDir, 'castle.json'), JSON.stringify(BARE_CASTLE_JSON, null, 2) + '\n');
|
|
180
|
+
fs.writeFileSync(path.join(projectDir, 'CLAUDE.md'), makeClaudeMd());
|
|
170
181
|
appendCommonInstructions(projectDir);
|
|
171
182
|
ensureAgentsSymlink(projectDir);
|
|
172
|
-
fs.writeFileSync(path.join(projectDir,
|
|
183
|
+
fs.writeFileSync(path.join(projectDir, 'package.json'), JSON.stringify(makePackageJson(projectDir), null, 2) + '\n');
|
|
173
184
|
}
|
|
174
185
|
// Copy a framework kit from kits/<kit>/ into the new deck dir, dropping
|
|
175
186
|
// build/dependency junk and castle.json.
|
|
@@ -180,7 +191,7 @@ function requireKitDir(kit) {
|
|
|
180
191
|
if (fs.existsSync(kitDir) && fs.statSync(kitDir).isDirectory())
|
|
181
192
|
return kitDir;
|
|
182
193
|
console.error(`Kit "${kit}" not found at ${kitDir}.`);
|
|
183
|
-
console.error(
|
|
194
|
+
console.error('Available kits:');
|
|
184
195
|
try {
|
|
185
196
|
const kits = fs
|
|
186
197
|
.readdirSync(getKitsDir())
|
|
@@ -189,34 +200,34 @@ function requireKitDir(kit) {
|
|
|
189
200
|
for (const name of kits)
|
|
190
201
|
console.error(` ${name}`);
|
|
191
202
|
else
|
|
192
|
-
console.error(
|
|
203
|
+
console.error(' (none)');
|
|
193
204
|
}
|
|
194
205
|
catch {
|
|
195
|
-
console.error(
|
|
206
|
+
console.error(' (none — kits/ directory is missing)');
|
|
196
207
|
}
|
|
197
|
-
console.error(
|
|
208
|
+
console.error('Or use `--kit none` for a bare code-only deck.');
|
|
198
209
|
return process.exit(1);
|
|
199
210
|
}
|
|
200
211
|
function readJsonFile(file) {
|
|
201
212
|
try {
|
|
202
|
-
return JSON.parse(fs.readFileSync(file,
|
|
213
|
+
return JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
203
214
|
}
|
|
204
215
|
catch {
|
|
205
216
|
return null;
|
|
206
217
|
}
|
|
207
218
|
}
|
|
208
219
|
function writeJsonFile(file, value) {
|
|
209
|
-
fs.writeFileSync(file, JSON.stringify(value, null, 2) +
|
|
220
|
+
fs.writeFileSync(file, JSON.stringify(value, null, 2) + '\n', 'utf8');
|
|
210
221
|
}
|
|
211
222
|
export function getCliVersion() {
|
|
212
|
-
const pkg = readJsonFile(path.join(getKitsDir(),
|
|
213
|
-
return typeof pkg?.version ===
|
|
223
|
+
const pkg = readJsonFile(path.join(getKitsDir(), '..', 'package.json'));
|
|
224
|
+
return typeof pkg?.version === 'string' ? pkg.version : '0.0.0';
|
|
214
225
|
}
|
|
215
226
|
// The deck's entry point is the KIT's -- the deck has no engine code of its own
|
|
216
227
|
// -- so index.html points into the import. Everything the kit's main.jsx imports
|
|
217
228
|
// is relative to itself, so it needs no rewriting to run from there.
|
|
218
|
-
function writeDeckIndexHtml(projectDir, alias, title) {
|
|
219
|
-
fs.writeFileSync(path.join(projectDir,
|
|
229
|
+
function writeDeckIndexHtml(projectDir, alias, title, mainFile) {
|
|
230
|
+
fs.writeFileSync(path.join(projectDir, 'index.html'), `<!doctype html>
|
|
220
231
|
<html>
|
|
221
232
|
<head>
|
|
222
233
|
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no" />
|
|
@@ -236,31 +247,34 @@ function writeDeckIndexHtml(projectDir, alias, title) {
|
|
|
236
247
|
<div id="root"></div>
|
|
237
248
|
<!-- Engine and editors come from the imported kit; this deck holds its own
|
|
238
249
|
scenes, drawings and behaviors. -->
|
|
239
|
-
<script type="module" src="/${IMPORTS_DIR}/${alias}
|
|
250
|
+
<script type="module" src="/${IMPORTS_DIR}/${alias}/${mainFile}"></script>
|
|
240
251
|
</body>
|
|
241
252
|
</html>
|
|
242
|
-
`,
|
|
253
|
+
`, 'utf8');
|
|
243
254
|
}
|
|
244
255
|
// The deck starts with its own copy of the kit's starter scene, so there is
|
|
245
256
|
// something to open and edit on the first run. Refs inside it are rewritten to
|
|
246
257
|
// point at the kit's copies of what they name (the scene is the deck's now, but
|
|
247
258
|
// the blueprint and art it places still live in the import).
|
|
248
|
-
|
|
249
|
-
|
|
259
|
+
// Give a deck the scene its kit says to start from, with every path that names a
|
|
260
|
+
// kit file rewritten to reach it through the import. Exported because both
|
|
261
|
+
// scaffold paths need it: `init --kit`, and adopting a kit via `add-import`.
|
|
262
|
+
export function writeStarterScene(projectDir, kitDir, alias) {
|
|
263
|
+
const scene = readJsonFile(path.join(kitDir, deckStarterScene(kitDir)));
|
|
250
264
|
const prefix = `${IMPORTS_DIR}/${alias}/`;
|
|
251
265
|
const rewrite = (value) => {
|
|
252
|
-
if (typeof value ===
|
|
266
|
+
if (typeof value === 'string') {
|
|
253
267
|
return fs.existsSync(path.join(kitDir, value)) ? prefix + value : value;
|
|
254
268
|
}
|
|
255
269
|
if (Array.isArray(value))
|
|
256
270
|
return value.map(rewrite);
|
|
257
|
-
if (value && typeof value ===
|
|
271
|
+
if (value && typeof value === 'object') {
|
|
258
272
|
return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, rewrite(v)]));
|
|
259
273
|
}
|
|
260
274
|
return value;
|
|
261
275
|
};
|
|
262
|
-
fs.mkdirSync(path.join(projectDir,
|
|
263
|
-
writeJsonFile(path.join(projectDir,
|
|
276
|
+
fs.mkdirSync(path.join(projectDir, 'scenes'), { recursive: true });
|
|
277
|
+
writeJsonFile(path.join(projectDir, 'scenes', 'main.scene'), scene ? rewrite(scene) : { name: 'Main', actors: [] });
|
|
264
278
|
}
|
|
265
279
|
function makeImportedKitClaudeMd(alias) {
|
|
266
280
|
return `# Castle deck
|
|
@@ -291,10 +305,17 @@ something in the kit behaves, define your own behavior of the same name in
|
|
|
291
305
|
function makeKitPin(kitConfig, kit) {
|
|
292
306
|
const deckId = kitConfig.deckId;
|
|
293
307
|
const version = kitConfig.publishedVersion;
|
|
294
|
-
if (typeof deckId ===
|
|
308
|
+
if (typeof deckId === 'string' && typeof version === 'string') {
|
|
295
309
|
return { deckId, version };
|
|
296
310
|
}
|
|
297
|
-
return { source:
|
|
311
|
+
return { source: 'builtin', kit, version: getCliVersion() };
|
|
312
|
+
}
|
|
313
|
+
// The kit's editor block minus `fileTypes` -- the layout and file filters a new
|
|
314
|
+
// deck starts from, without the vocabulary it resolves from the kit itself.
|
|
315
|
+
function withoutFileTypes(editor) {
|
|
316
|
+
const rest = { ...(editor ?? {}) };
|
|
317
|
+
delete rest.fileTypes;
|
|
318
|
+
return rest;
|
|
298
319
|
}
|
|
299
320
|
// Scaffold a deck that IMPORTS its kit instead of copying it. The kit's files
|
|
300
321
|
// land in `imports/<kit>/` (read-only, like any dependency) and the deck itself
|
|
@@ -323,16 +344,22 @@ function scaffoldFromKitImport(kit, projectDir) {
|
|
|
323
344
|
verbatimSymlinks: true,
|
|
324
345
|
filter: (src) => src === kitDir || !KIT_COPY_EXCLUDE.has(path.basename(src)),
|
|
325
346
|
});
|
|
326
|
-
const kitConfig = readJsonFile(path.join(kitDir,
|
|
327
|
-
const kitPkg = readJsonFile(path.join(kitDir,
|
|
328
|
-
const title = typeof kitConfig.title ===
|
|
329
|
-
writeDeckIndexHtml(projectDir, alias, title);
|
|
347
|
+
const kitConfig = readJsonFile(path.join(kitDir, 'castle.json')) ?? {};
|
|
348
|
+
const kitPkg = readJsonFile(path.join(kitDir, 'package.json')) ?? {};
|
|
349
|
+
const title = typeof kitConfig.title === 'string' ? kitConfig.title : kit;
|
|
350
|
+
writeDeckIndexHtml(projectDir, alias, title, deckMainFile(kitDir));
|
|
330
351
|
writeStarterScene(projectDir, kitDir, alias);
|
|
331
352
|
// The deck's own castle.json: the kit's editor config (panel layout, file
|
|
332
353
|
// filters) as a starting point -- it is the deck's to edit from here -- plus
|
|
333
354
|
// the import pin.
|
|
334
|
-
|
|
335
|
-
|
|
355
|
+
//
|
|
356
|
+
// `fileTypes` is deliberately NOT copied. It is the kit's own vocabulary, and
|
|
357
|
+
// the deck resolves it live from every import's castle.json (see
|
|
358
|
+
// editorConfig.resolveFileTypes) -- so the kit keeps owning it, a kit fix
|
|
359
|
+
// reaches the deck like any other, and a kit imported LATER contributes its
|
|
360
|
+
// types too, which a copy taken at scaffold time never could.
|
|
361
|
+
writeJsonFile(path.join(projectDir, 'castle.json'), {
|
|
362
|
+
...(kitConfig.editor ? { editor: withoutFileTypes(kitConfig.editor) } : {}),
|
|
336
363
|
imports: { [alias]: makeKitPin(kitConfig, kit) },
|
|
337
364
|
});
|
|
338
365
|
// The kit's code runs from THIS deck's node_modules, so its dependencies are
|
|
@@ -342,18 +369,16 @@ function scaffoldFromKitImport(kit, projectDir) {
|
|
|
342
369
|
const dependencies = {};
|
|
343
370
|
for (const [name, range] of Object.entries(kitPkg.dependencies ?? {})) {
|
|
344
371
|
dependencies[name] =
|
|
345
|
-
name ===
|
|
346
|
-
? sdkRef
|
|
347
|
-
: String(range);
|
|
372
|
+
name === 'castle-web-sdk' && String(range).startsWith('file:') ? sdkRef : String(range);
|
|
348
373
|
}
|
|
349
|
-
writeJsonFile(path.join(projectDir,
|
|
374
|
+
writeJsonFile(path.join(projectDir, 'package.json'), {
|
|
350
375
|
name: path.basename(projectDir),
|
|
351
376
|
private: true,
|
|
352
|
-
type:
|
|
377
|
+
type: 'module',
|
|
353
378
|
scripts: makeKitDeckScripts(kitDir, alias, cliCommand),
|
|
354
379
|
dependencies,
|
|
355
380
|
});
|
|
356
|
-
fs.writeFileSync(path.join(projectDir,
|
|
381
|
+
fs.writeFileSync(path.join(projectDir, 'CLAUDE.md'), makeImportedKitClaudeMd(alias));
|
|
357
382
|
appendCommonInstructions(projectDir);
|
|
358
383
|
lockImportTree(importDir);
|
|
359
384
|
}
|
|
@@ -364,14 +389,14 @@ export async function init(dir, opts = {}) {
|
|
|
364
389
|
process.exit(1);
|
|
365
390
|
}
|
|
366
391
|
const kit = opts.kit ?? DEFAULT_KIT;
|
|
367
|
-
const bare = kit ===
|
|
392
|
+
const bare = kit === 'none' || kit === 'bare';
|
|
368
393
|
if (bare) {
|
|
369
394
|
scaffoldBare(projectDir);
|
|
370
395
|
}
|
|
371
396
|
else {
|
|
372
397
|
scaffoldFromKitImport(kit, projectDir);
|
|
373
398
|
}
|
|
374
|
-
console.log(`Created project in ${projectDir}/${bare ?
|
|
399
|
+
console.log(`Created project in ${projectDir}/${bare ? '' : ` (from kit "${kit}")`}`);
|
|
375
400
|
// Always install deps so the deck is ready to serve/edit immediately.
|
|
376
401
|
// `--no-serve` only skips the serve step below (callers like the cloud
|
|
377
402
|
// launcher run their own serve, but still want deps in place).
|
|
@@ -381,18 +406,18 @@ export async function init(dir, opts = {}) {
|
|
|
381
406
|
// decks rewrite the sdk to file:../../sdk, which the shipped (published)
|
|
382
407
|
// lockfile won't match -> drop it and let pnpm resolve. Bare decks have no
|
|
383
408
|
// lockfile -> non-frozen.
|
|
384
|
-
const lockPath = path.join(projectDir,
|
|
409
|
+
const lockPath = path.join(projectDir, 'pnpm-lock.yaml');
|
|
385
410
|
if (resolveScaffoldRefs().workspaceMode)
|
|
386
411
|
fs.rmSync(lockPath, { force: true });
|
|
387
412
|
const frozen = fs.existsSync(lockPath);
|
|
388
|
-
console.log(
|
|
413
|
+
console.log('');
|
|
389
414
|
let installed = false;
|
|
390
415
|
try {
|
|
391
|
-
installDeps(projectDir, frozen);
|
|
416
|
+
await installDeps(projectDir, frozen);
|
|
392
417
|
installed = true;
|
|
393
418
|
}
|
|
394
419
|
catch {
|
|
395
|
-
console.error(
|
|
420
|
+
console.error('dependency install failed; re-run `pnpm install` (or `npm install`) in the deck.');
|
|
396
421
|
}
|
|
397
422
|
const autoServe = opts.serve !== false;
|
|
398
423
|
if (autoServe && installed) {
|
|
@@ -402,16 +427,16 @@ export async function init(dir, opts = {}) {
|
|
|
402
427
|
// the served page; users can override host on a subsequent serve call.
|
|
403
428
|
// Open in the user's default browser unless we're clearly headless (SSH
|
|
404
429
|
// session) or the user has opted out via CASTLE_WEB_CLI_NO_OPEN=1.
|
|
405
|
-
const noOpen = process.env.CASTLE_WEB_CLI_NO_OPEN ===
|
|
430
|
+
const noOpen = process.env.CASTLE_WEB_CLI_NO_OPEN === '1' ||
|
|
406
431
|
!!process.env.SSH_CONNECTION ||
|
|
407
432
|
!!process.env.SSH_TTY;
|
|
408
|
-
await serve(projectDir, { host:
|
|
433
|
+
await serve(projectDir, { host: '0.0.0.0', detach: true, open: !noOpen });
|
|
409
434
|
return;
|
|
410
435
|
}
|
|
411
|
-
console.log(
|
|
412
|
-
console.log(
|
|
436
|
+
console.log('');
|
|
437
|
+
console.log('Next steps:');
|
|
413
438
|
console.log(` cd ${dir}`);
|
|
414
439
|
if (!installed)
|
|
415
|
-
console.log(
|
|
416
|
-
console.log(
|
|
440
|
+
console.log(' pnpm install # or: npm install');
|
|
441
|
+
console.log(' castle-web serve . # & in your shell to background it');
|
|
417
442
|
}
|
package/dist/install.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare function installDeps(projectDir: string, frozen: boolean): void
|
|
1
|
+
export declare function installDeps(projectDir: string, frozen: boolean): Promise<void>;
|
|
2
2
|
export declare function install(dir: string): Promise<void>;
|