unoverse 0.1.5 → 0.1.7
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/unoverse.mjs +51 -13
- package/lib/create.mjs +56 -27
- package/lib/urls.mjs +3 -1
- package/package.json +1 -1
package/bin/unoverse.mjs
CHANGED
|
@@ -9,21 +9,58 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import { existsSync } from "node:fs";
|
|
11
11
|
import { spawnSync } from "node:child_process";
|
|
12
|
+
import { join, resolve } from "node:path";
|
|
12
13
|
import { create } from "../lib/create.mjs";
|
|
13
14
|
|
|
15
|
+
/**
|
|
16
|
+
* ONE BINARY. There is no `./unoverse` any more.
|
|
17
|
+
*
|
|
18
|
+
* A universe is a folder holding the operator lib. Walk up for it, the same way git finds
|
|
19
|
+
* a repo, so the commands work from anywhere inside your universe rather than only at its
|
|
20
|
+
* root. Found means the operator commands exist; not found means they simply are not
|
|
21
|
+
* offered, instead of being advertised and then refusing.
|
|
22
|
+
*/
|
|
23
|
+
function findUniverse() {
|
|
24
|
+
for (let dir = process.cwd(); ; ) {
|
|
25
|
+
if (existsSync(join(dir, "scripts/lib/common.sh"))) return dir;
|
|
26
|
+
const parent = resolve(dir, "..");
|
|
27
|
+
if (parent === dir) return null;
|
|
28
|
+
dir = parent;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Run an operator command in the universe we are standing in. */
|
|
33
|
+
function operator(root, args) {
|
|
34
|
+
const r = spawnSync("bash", [join(root, "scripts/operator.sh"), ...args], {
|
|
35
|
+
stdio: "inherit",
|
|
36
|
+
cwd: root,
|
|
37
|
+
});
|
|
38
|
+
process.exit(r.status ?? 0);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const UNIVERSE = findUniverse();
|
|
42
|
+
|
|
43
|
+
// Everything the operator half owns. Listed here so an unknown command can say so,
|
|
44
|
+
// rather than the two CLIs disagreeing about what exists.
|
|
45
|
+
const OPERATOR_COMMANDS = new Set([
|
|
46
|
+
"start", "stop", "check", "logs", "deploy",
|
|
47
|
+
// kept working, not advertised
|
|
48
|
+
"ground", "dev", "build", "publish", "init", "doctor", "db-setup", "db-verify", "open",
|
|
49
|
+
]);
|
|
50
|
+
|
|
14
51
|
const [, , cmd, ...args] = process.argv;
|
|
15
52
|
|
|
16
53
|
const HELP = `
|
|
17
54
|
⬡ unoverse
|
|
18
55
|
|
|
19
56
|
unoverse create [name] What are you building? Studio project · universe · client app
|
|
57
|
+
No name scaffolds into the folder you are in; a name makes one
|
|
20
58
|
unoverse studio Launch Unoverse Studio (authoring: components, nodes, skills)
|
|
21
|
-
unoverse
|
|
59
|
+
unoverse where Where your universe is — addresses probed live, not recited
|
|
22
60
|
unoverse update Update this CLI to the latest version
|
|
23
61
|
unoverse help This
|
|
24
62
|
|
|
25
|
-
|
|
26
|
-
cd <your-universe> && ./unoverse
|
|
63
|
+
Standing in a universe folder adds: start · stop · check · logs · deploy
|
|
27
64
|
`;
|
|
28
65
|
|
|
29
66
|
switch (cmd) {
|
|
@@ -32,12 +69,9 @@ switch (cmd) {
|
|
|
32
69
|
break;
|
|
33
70
|
|
|
34
71
|
case "update": {
|
|
35
|
-
//
|
|
36
|
-
//
|
|
37
|
-
if (
|
|
38
|
-
console.log(`\n This folder is a universe — its own CLI owns updates:\n\n ./unoverse update\n`);
|
|
39
|
-
process.exit(1);
|
|
40
|
-
}
|
|
72
|
+
// In a universe, "update" means THAT UNIVERSE (pull images, restart), not this CLI.
|
|
73
|
+
// Same word, different object, decided by where you are standing.
|
|
74
|
+
if (UNIVERSE) operator(UNIVERSE, ["update", ...args]);
|
|
41
75
|
const r = spawnSync("npm", ["install", "-g", "unoverse@latest"], { stdio: "inherit" });
|
|
42
76
|
if (r.status === 0) {
|
|
43
77
|
const v = spawnSync("unoverse", ["-v"], { encoding: "utf8" }).stdout?.trim();
|
|
@@ -48,6 +82,8 @@ switch (cmd) {
|
|
|
48
82
|
process.exit(r.status ?? 0);
|
|
49
83
|
}
|
|
50
84
|
|
|
85
|
+
// `urls` stays as an alias: it shipped in 0.1.4/0.1.5 and is in people's fingers.
|
|
86
|
+
case "where":
|
|
51
87
|
case "urls": {
|
|
52
88
|
const { urls } = await import("../lib/urls.mjs");
|
|
53
89
|
await urls();
|
|
@@ -66,6 +102,8 @@ switch (cmd) {
|
|
|
66
102
|
case "--help":
|
|
67
103
|
case "-h":
|
|
68
104
|
case undefined:
|
|
105
|
+
// In a universe, its own help prints — it knows whether this kit has the owner lane.
|
|
106
|
+
if (UNIVERSE) operator(UNIVERSE, ["help"]);
|
|
69
107
|
console.log(HELP);
|
|
70
108
|
break;
|
|
71
109
|
|
|
@@ -81,10 +119,10 @@ switch (cmd) {
|
|
|
81
119
|
}
|
|
82
120
|
|
|
83
121
|
default:
|
|
84
|
-
//
|
|
85
|
-
|
|
86
|
-
if (
|
|
87
|
-
console.log(`\n '${cmd}'
|
|
122
|
+
// The operator half, run right here. No second CLI to be sent to.
|
|
123
|
+
if (UNIVERSE && OPERATOR_COMMANDS.has(cmd)) operator(UNIVERSE, [cmd, ...args]);
|
|
124
|
+
if (OPERATOR_COMMANDS.has(cmd)) {
|
|
125
|
+
console.log(`\n '${cmd}' operates a universe, and this folder is not one.\n ${"cd into your universe, or run: unoverse create"}\n`);
|
|
88
126
|
} else {
|
|
89
127
|
console.log(`\n Unknown command: ${cmd}\n${HELP}`);
|
|
90
128
|
}
|
package/lib/create.mjs
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* a real auth round-trip against the registry, not a format check.
|
|
9
9
|
*/
|
|
10
10
|
import { createInterface } from "node:readline/promises";
|
|
11
|
-
import { existsSync, mkdirSync } from "node:fs";
|
|
11
|
+
import { existsSync, mkdirSync, readdirSync } from "node:fs";
|
|
12
12
|
import { spawnSync } from "node:child_process";
|
|
13
13
|
|
|
14
14
|
const REGISTRY_HOST = "registry.digitalocean.com";
|
|
@@ -56,15 +56,43 @@ async function validateRegistryToken(token) {
|
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
|
|
59
|
+
/**
|
|
60
|
+
* Where to scaffold. NO NAME MEANS THE FOLDER YOU ARE STANDING IN.
|
|
61
|
+
*
|
|
62
|
+
* That is what a Studio project has always done (option 1 hands the cwd to Studio), so
|
|
63
|
+
* the other two paths agreeing removes a difference nobody could have guessed. Passing a
|
|
64
|
+
* name still makes a subfolder: `unoverse create acme` → ./acme.
|
|
65
|
+
*
|
|
66
|
+
* The old guard refused any target that EXISTED, which made "." impossible — the folder
|
|
67
|
+
* you are in always exists, so it failed with "./. already exists". Existence was never
|
|
68
|
+
* the question; whether there is anything to overwrite is. `.git` and `.DS_Store` do not
|
|
69
|
+
* count, so scaffolding into a folder you just `git init`ed works.
|
|
70
|
+
*/
|
|
71
|
+
function resolveTarget(nameArg) {
|
|
72
|
+
const dir = nameArg || ".";
|
|
73
|
+
const here = dir === ".";
|
|
74
|
+
const existing = existsSync(dir)
|
|
75
|
+
? readdirSync(dir).filter((f) => f !== ".git" && f !== ".DS_Store")
|
|
76
|
+
: [];
|
|
77
|
+
if (existing.length) {
|
|
78
|
+
fail(here ? "this folder is not empty" : `./${dir} is not empty`);
|
|
79
|
+
console.log(` ${dim("Scaffolding would overwrite what is here. Use an empty folder, or pass a name.")}`);
|
|
80
|
+
process.exit(1);
|
|
81
|
+
}
|
|
82
|
+
return dir;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Human name for the target, for messages that should not say "./.". */
|
|
86
|
+
const label = (dir) => (dir === "." ? "this folder" : `./${dir}`);
|
|
87
|
+
|
|
88
|
+
function download(dir, stripComponents = 1, filter = "") {
|
|
60
89
|
mkdirSync(dir, { recursive: true });
|
|
61
90
|
const r = spawnSync(
|
|
62
91
|
"sh",
|
|
63
|
-
["-c", `curl -fsSL '${STARTER_TARBALL}' | tar -xz --strip-components
|
|
92
|
+
["-c", `curl -fsSL '${STARTER_TARBALL}' | tar -xz --strip-components=${stripComponents} -C '${dir}' ${filter}`],
|
|
64
93
|
{ stdio: ["ignore", "inherit", "inherit"] },
|
|
65
94
|
);
|
|
66
95
|
if (r.status !== 0) throw new Error("download failed");
|
|
67
|
-
ok(`universe scaffolded at ./${name}`);
|
|
68
96
|
}
|
|
69
97
|
|
|
70
98
|
export async function create(nameArg) {
|
|
@@ -102,17 +130,28 @@ export async function create(nameArg) {
|
|
|
102
130
|
}
|
|
103
131
|
ok("token accepted");
|
|
104
132
|
|
|
105
|
-
const name = nameArg
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
133
|
+
const name = resolveTarget(nameArg);
|
|
134
|
+
download(name);
|
|
135
|
+
ok(`universe scaffolded in ${label(name)}`);
|
|
136
|
+
|
|
137
|
+
// CREATE FINISHES THE JOB. It used to stop here and tell you to go and run
|
|
138
|
+
// `./unoverse init`, which asked for this same token a second time. The token is
|
|
139
|
+
// already in hand and already validated, so hand it over and configure now.
|
|
140
|
+
rl.close();
|
|
141
|
+
console.log("");
|
|
142
|
+
const r = spawnSync("bash", [`${name}/scripts/operator.sh`, "init"], {
|
|
143
|
+
stdio: "inherit",
|
|
144
|
+
cwd: name,
|
|
145
|
+
env: { ...process.env, UNOVERSE_DOCR_TOKEN: token },
|
|
146
|
+
});
|
|
147
|
+
if (r.status !== 0) {
|
|
148
|
+
fail("setup did not finish — run 'unoverse init' in that folder to pick it up");
|
|
149
|
+
process.exit(r.status ?? 1);
|
|
109
150
|
}
|
|
110
|
-
download(name, name);
|
|
111
151
|
console.log(`
|
|
112
|
-
Next:
|
|
113
|
-
cd ${name}
|
|
114
|
-
|
|
115
|
-
./unoverse start
|
|
152
|
+
Next:${name === "." ? "" : `
|
|
153
|
+
cd ${name}`}
|
|
154
|
+
unoverse start
|
|
116
155
|
|
|
117
156
|
${cyan("Build with Claude:")} open the folder in Claude Code — it ships an
|
|
118
157
|
authoring skill (components, nodes, agent skills) and a builder MCP that
|
|
@@ -122,21 +161,11 @@ export async function create(nameArg) {
|
|
|
122
161
|
}
|
|
123
162
|
|
|
124
163
|
if (choice === "3") {
|
|
125
|
-
const name = nameArg
|
|
126
|
-
if (existsSync(name)) {
|
|
127
|
-
fail(`./${name} already exists`);
|
|
128
|
-
process.exit(1);
|
|
129
|
-
}
|
|
164
|
+
const name = resolveTarget(nameArg);
|
|
130
165
|
// The reference client ships inside the starter — take just that folder.
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
["-c", `curl -fsSL '${STARTER_TARBALL}' | tar -xz --strip-components=3 -C '${name}' '*/packages/client'`],
|
|
135
|
-
{ stdio: ["ignore", "inherit", "inherit"] },
|
|
136
|
-
);
|
|
137
|
-
if (r.status !== 0 ) throw new Error("download failed");
|
|
138
|
-
ok(`client app scaffolded at ./${name}`);
|
|
139
|
-
console.log(`\n Next:\n cd ${name}\n npm install && npm run dev\n\n ${cyan("Build with Claude:")} open the folder in Claude Code and ask for what you want.\n`);
|
|
166
|
+
download(name, 3, "'*/packages/client'");
|
|
167
|
+
ok(`client app scaffolded in ${label(name)}`);
|
|
168
|
+
console.log(`\n Next:${name === "." ? "" : `\n cd ${name}`}\n npm install && npm run dev\n\n ${cyan("Build with Claude:")} open the folder in Claude Code and ask for what you want.\n`);
|
|
140
169
|
return;
|
|
141
170
|
}
|
|
142
171
|
|
package/lib/urls.mjs
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `unoverse
|
|
2
|
+
* `unoverse where` — the deployed stack's addresses, PROBED, not recited.
|
|
3
|
+
*
|
|
4
|
+
* Named `urls` until 0.1.5; that spelling still works as an alias.
|
|
3
5
|
*
|
|
4
6
|
* Run it in a universe folder (or anywhere above one). It reads the deployment's
|
|
5
7
|
* own facts — .env.production first, the ground's terraform outputs as enrichment —
|
package/package.json
CHANGED