@domino-sdk/relay-cli 0.4.0 → 0.6.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/cli/capabilities.json +98 -14
- package/cli/commands/agents.mjs +20 -8
- package/cli/commands/discovery.mjs +23 -4
- package/cli/commands/hosting.mjs +65 -11
- package/cli/commands/inspect.mjs +16 -9
- package/cli/commands/project.mjs +22 -14
- package/cli/commands/staging.mjs +44 -9
- package/cli/connected-dev.mjs +4 -2
- package/cli/dev.mjs +1 -1
- package/cli/doctor.mjs +17 -2
- package/cli/git.mjs +38 -7
- package/cli/output.mjs +76 -0
- package/cli/package-manager.mjs +31 -0
- package/cli/project.mjs +16 -12
- package/cli/prompts.mjs +92 -0
- package/cli/runtime.mjs +16 -7
- package/cli/select-project.mjs +64 -0
- package/cli/skills/domino/SKILL.md +3 -3
- package/cli/skills/domino/references/authoring.md +60 -3
- package/cli/skills/domino/references/hosted.md +3 -3
- package/cli/skills/domino/references/participant-api.md +22 -3
- package/cli/skills/domino/references/troubleshooting.md +18 -0
- package/cli.mjs +6 -2
- package/dist/index.d.ts +1306 -26
- package/dist/index.js +119 -6
- package/package.json +2 -2
package/cli/capabilities.json
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
{
|
|
21
21
|
"id": "connected-accounts",
|
|
22
22
|
"support": "native",
|
|
23
|
-
"description": "
|
|
23
|
+
"description": "Project-scoped Discord membership, roles, nickname, display name, boosting status and captured message reads. Participants link identity; operators install the bot once. No arbitrary Discord writes or X verification.",
|
|
24
24
|
"entrypoints": [
|
|
25
25
|
"auth.accounts",
|
|
26
26
|
"auth.start",
|
|
@@ -43,33 +43,72 @@
|
|
|
43
43
|
"apps/api/src/identity-integration.ts"
|
|
44
44
|
]
|
|
45
45
|
},
|
|
46
|
+
{
|
|
47
|
+
"id": "observation-quests",
|
|
48
|
+
"support": "native",
|
|
49
|
+
"description": "Trusted backend observations and Discord reaction.add, reaction.received and message.create trigger automatic quests. Actor identity is separate from beneficiary membership. Keyed completions and atomic budgets prevent duplicate awards. Multi-action counters and streaks are not implemented.",
|
|
50
|
+
"entrypoints": [
|
|
51
|
+
"automatic",
|
|
52
|
+
"keyed",
|
|
53
|
+
"budget",
|
|
54
|
+
"management.observations.submit"
|
|
55
|
+
],
|
|
56
|
+
"reference": "authoring",
|
|
57
|
+
"evidence": [
|
|
58
|
+
"tests/observation-quests.test.mjs",
|
|
59
|
+
"tests/discord-observations.test.mjs"
|
|
60
|
+
]
|
|
61
|
+
},
|
|
46
62
|
{
|
|
47
63
|
"id": "quests",
|
|
48
64
|
"support": "native",
|
|
49
65
|
"description": "Claim, photo, quiz, staff and automatic quests; immutable releases pin attempts and promised rewards.",
|
|
50
|
-
"entrypoints": [
|
|
51
|
-
|
|
66
|
+
"entrypoints": [
|
|
67
|
+
"quests.experience",
|
|
68
|
+
"quests.observe",
|
|
69
|
+
"quests.open"
|
|
70
|
+
],
|
|
71
|
+
"evidence": [
|
|
72
|
+
"tests/quest-catalog.test.mjs",
|
|
73
|
+
"tests/submissions.test.mjs"
|
|
74
|
+
]
|
|
52
75
|
},
|
|
53
76
|
{
|
|
54
77
|
"id": "points",
|
|
55
78
|
"support": "native",
|
|
56
79
|
"description": "Award points to the participant completing a quest. The server applies completion and rewards; the browser cannot write balances.",
|
|
57
|
-
"entrypoints": [
|
|
58
|
-
|
|
80
|
+
"entrypoints": [
|
|
81
|
+
"points",
|
|
82
|
+
"decision.accept",
|
|
83
|
+
"me.observeProgress"
|
|
84
|
+
],
|
|
85
|
+
"evidence": [
|
|
86
|
+
"tests/journey.test.mjs"
|
|
87
|
+
]
|
|
59
88
|
},
|
|
60
89
|
{
|
|
61
90
|
"id": "review",
|
|
62
91
|
"support": "native",
|
|
63
92
|
"description": "Queue a quest attempt for an authorized human reviewer. Review decisions and reward execution remain server-owned.",
|
|
64
|
-
"entrypoints": [
|
|
65
|
-
|
|
93
|
+
"entrypoints": [
|
|
94
|
+
"decision.review"
|
|
95
|
+
],
|
|
96
|
+
"evidence": [
|
|
97
|
+
"tests/photo-review.test.mjs"
|
|
98
|
+
]
|
|
66
99
|
},
|
|
67
100
|
{
|
|
68
101
|
"id": "milestones",
|
|
69
102
|
"support": "composed",
|
|
70
103
|
"description": "Compose same-participant completion prerequisites with requires: [completed(quest)]. Referral programs support milestones based on distinct qualified friends. Other aggregate thresholds need application logic.",
|
|
71
|
-
"entrypoints": [
|
|
72
|
-
|
|
104
|
+
"entrypoints": [
|
|
105
|
+
"completed",
|
|
106
|
+
"automatic",
|
|
107
|
+
"defineQuest"
|
|
108
|
+
],
|
|
109
|
+
"evidence": [
|
|
110
|
+
"tests/availability.test.mjs"
|
|
111
|
+
]
|
|
73
112
|
},
|
|
74
113
|
{
|
|
75
114
|
"id": "referrals",
|
|
@@ -83,15 +122,24 @@
|
|
|
83
122
|
"referrals.history"
|
|
84
123
|
],
|
|
85
124
|
"reference": "referrals",
|
|
86
|
-
"evidence": [
|
|
125
|
+
"evidence": [
|
|
126
|
+
"tests/referrals.test.mjs",
|
|
127
|
+
"packages/sdk/src/referrals.ts"
|
|
128
|
+
]
|
|
87
129
|
},
|
|
88
130
|
{
|
|
89
131
|
"id": "cross-participant-rewards",
|
|
90
132
|
"support": "native",
|
|
91
133
|
"description": "Referral programs can reward inviter and invitee independently and award a selective percentage of eligible points to the inviter. Bonuses accumulate fractions and support limits, suspension and correction recovery. Arbitrary participant-to-participant transfers and multi-level commissions are not supported.",
|
|
92
|
-
"entrypoints": [
|
|
134
|
+
"entrypoints": [
|
|
135
|
+
"defineReferral",
|
|
136
|
+
"referrals.summary"
|
|
137
|
+
],
|
|
93
138
|
"reference": "referrals",
|
|
94
|
-
"evidence": [
|
|
139
|
+
"evidence": [
|
|
140
|
+
"tests/referrals.test.mjs",
|
|
141
|
+
"apps/api/src/referrals.ts"
|
|
142
|
+
]
|
|
95
143
|
},
|
|
96
144
|
{
|
|
97
145
|
"id": "rankings",
|
|
@@ -111,8 +159,44 @@
|
|
|
111
159
|
"id": "wallet-verification",
|
|
112
160
|
"support": "native",
|
|
113
161
|
"description": "Connect a wallet and verify supported Solana balances. This reads a real network; it does not send assets or execute payouts.",
|
|
114
|
-
"entrypoints": [
|
|
115
|
-
|
|
162
|
+
"entrypoints": [
|
|
163
|
+
"wallet.connection",
|
|
164
|
+
"wallet.challenge",
|
|
165
|
+
"wallet.verify"
|
|
166
|
+
],
|
|
167
|
+
"evidence": [
|
|
168
|
+
"tests/wallet.test.mjs"
|
|
169
|
+
]
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
"id": "resource-settings",
|
|
173
|
+
"support": "native",
|
|
174
|
+
"description": "Reusable Console resource and date/time fields. Discord channels resolve from the connected project integration; backend publication validates access. Empty resource settings keep code-authored quests paused.",
|
|
175
|
+
"entrypoints": [
|
|
176
|
+
"settings.resource",
|
|
177
|
+
"settings.datetime",
|
|
178
|
+
"discord.channels",
|
|
179
|
+
"discord.roles"
|
|
180
|
+
],
|
|
181
|
+
"reference": "authoring",
|
|
182
|
+
"evidence": [
|
|
183
|
+
"tests/resource-settings.test.mjs"
|
|
184
|
+
]
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
"id": "discord-role-rewards",
|
|
188
|
+
"support": "native",
|
|
189
|
+
"description": "Quest-declared Discord role rewards commit durable delivery requests with completion, retry independently, and remain bound to the original account/server. Role selectors validate assignability. Referral reward lists do not accept integration rewards.",
|
|
190
|
+
"entrypoints": [
|
|
191
|
+
"discordRole",
|
|
192
|
+
"me.rewardDeliveries",
|
|
193
|
+
"management.rewardDeliveries"
|
|
194
|
+
],
|
|
195
|
+
"reference": "authoring",
|
|
196
|
+
"evidence": [
|
|
197
|
+
"tests/discord-role-delivery.test.mjs",
|
|
198
|
+
"tests/discord-expansion.test.mjs"
|
|
199
|
+
]
|
|
116
200
|
}
|
|
117
201
|
]
|
|
118
202
|
}
|
package/cli/commands/agents.mjs
CHANGED
|
@@ -2,6 +2,7 @@ import { Option } from "commander";
|
|
|
2
2
|
import { dirname } from "node:path";
|
|
3
3
|
import { action } from "../runtime.mjs";
|
|
4
4
|
import { installAgentSkills } from "../agents.mjs";
|
|
5
|
+
import { canPrompt, promptSelect } from "../prompts.mjs";
|
|
5
6
|
import { diagnose } from "../doctor.mjs";
|
|
6
7
|
|
|
7
8
|
export function registerAgentCommands(program) {
|
|
@@ -17,18 +18,29 @@ export function registerAgentCommands(program) {
|
|
|
17
18
|
"Project root (defaults to the linked project or current directory)",
|
|
18
19
|
)
|
|
19
20
|
.addOption(
|
|
20
|
-
new Option("--agent <name>", "Agent to configure")
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
new Option("--agent <name>", "Agent to configure").choices([
|
|
22
|
+
"codex",
|
|
23
|
+
"claude",
|
|
24
|
+
"both",
|
|
25
|
+
]),
|
|
23
26
|
)
|
|
24
27
|
.action(
|
|
25
|
-
action(({ project, options }) =>
|
|
26
|
-
|
|
28
|
+
action(async ({ project, options }) => {
|
|
29
|
+
const agent =
|
|
30
|
+
options.agent ??
|
|
31
|
+
(canPrompt(options)
|
|
32
|
+
? await promptSelect("Choose your coding agent", [
|
|
33
|
+
{ label: "Codex", value: "codex" },
|
|
34
|
+
{ label: "Claude", value: "claude" },
|
|
35
|
+
{ label: "Both", value: "both" },
|
|
36
|
+
])
|
|
37
|
+
: "both");
|
|
38
|
+
return installAgentSkills(
|
|
27
39
|
options.directory ??
|
|
28
40
|
(project ? dirname(project.path) : process.cwd()),
|
|
29
|
-
|
|
30
|
-
)
|
|
31
|
-
),
|
|
41
|
+
agent,
|
|
42
|
+
);
|
|
43
|
+
}),
|
|
32
44
|
);
|
|
33
45
|
|
|
34
46
|
program
|
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
import { readFile } from "node:fs/promises";
|
|
2
2
|
import { action } from "../runtime.mjs";
|
|
3
3
|
|
|
4
|
-
const references = [
|
|
4
|
+
const references = [
|
|
5
|
+
"authoring",
|
|
6
|
+
"participant-api",
|
|
7
|
+
"referrals",
|
|
8
|
+
"troubleshooting",
|
|
9
|
+
];
|
|
5
10
|
export function registerDiscoveryCommands(program) {
|
|
6
11
|
program
|
|
7
12
|
.command("capabilities")
|
|
8
|
-
.description(
|
|
13
|
+
.description(
|
|
14
|
+
"Read this CLI release's bundled capability catalog (not a server probe)",
|
|
15
|
+
)
|
|
9
16
|
.argument("[id]", "Capability identifier")
|
|
10
17
|
.action(
|
|
11
18
|
action(async (_, id) => {
|
|
@@ -15,13 +22,25 @@ export function registerDiscoveryCommands(program) {
|
|
|
15
22
|
"utf8",
|
|
16
23
|
),
|
|
17
24
|
);
|
|
18
|
-
|
|
25
|
+
const { version } = JSON.parse(
|
|
26
|
+
await readFile(
|
|
27
|
+
new URL("../../package.json", import.meta.url),
|
|
28
|
+
"utf8",
|
|
29
|
+
),
|
|
30
|
+
);
|
|
31
|
+
const provenance = {
|
|
32
|
+
kind: "bundled-catalog",
|
|
33
|
+
cliVersion: version,
|
|
34
|
+
serverVerified: false,
|
|
35
|
+
note: "Describes this CLI release. Scope flags do not make it project-specific. Verify the deployed contract before treating missing or conflicting entries as unsupported.",
|
|
36
|
+
};
|
|
37
|
+
if (!id) return { ...catalog, provenance };
|
|
19
38
|
const capability = catalog.capabilities.find((item) => item.id === id);
|
|
20
39
|
if (!capability)
|
|
21
40
|
throw new Error(
|
|
22
41
|
`Unknown capability. Choose: ${catalog.capabilities.map((item) => item.id).join(", ")}`,
|
|
23
42
|
);
|
|
24
|
-
return capability;
|
|
43
|
+
return { ...capability, provenance };
|
|
25
44
|
}),
|
|
26
45
|
);
|
|
27
46
|
program
|
package/cli/commands/hosting.mjs
CHANGED
|
@@ -4,6 +4,8 @@ import { dirname, resolve } from "node:path";
|
|
|
4
4
|
import { execFile } from "node:child_process";
|
|
5
5
|
import { promisify } from "node:util";
|
|
6
6
|
import { action } from "../runtime.mjs";
|
|
7
|
+
import { canPrompt, promptText, promptSelect } from "../prompts.mjs";
|
|
8
|
+
import { selectProject } from "../select-project.mjs";
|
|
7
9
|
import { request } from "../connection.mjs";
|
|
8
10
|
import { checkout, requireNewDirectory } from "../git.mjs";
|
|
9
11
|
import { dev } from "../dev.mjs";
|
|
@@ -69,20 +71,54 @@ export function registerHostingCommands(program) {
|
|
|
69
71
|
program
|
|
70
72
|
.command("create")
|
|
71
73
|
.description("Create a hosted campaign repository and check it out locally")
|
|
72
|
-
.argument("
|
|
74
|
+
.argument("[name]", "Project identifier (prompted if omitted)")
|
|
73
75
|
.option("--directory <path>", "Local directory (defaults to project name)")
|
|
74
76
|
.action(
|
|
75
77
|
action(async ({ connection, options }, name) => {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
78
|
+
const interactive = canPrompt(options);
|
|
79
|
+
const validateName = (value) =>
|
|
80
|
+
/^[a-zA-Z0-9_-]+$/.test(value)
|
|
81
|
+
? undefined
|
|
82
|
+
: "Use letters, numbers, underscores, or hyphens for the project name.";
|
|
83
|
+
if (!name && interactive)
|
|
84
|
+
name = await promptText("Project name", { validate: validateName });
|
|
85
|
+
if (!name)
|
|
86
|
+
throw new Error("Provide a project name: domino create NAME.");
|
|
87
|
+
const nameError = validateName(name);
|
|
88
|
+
if (nameError) throw new Error(nameError);
|
|
89
|
+
const destination =
|
|
90
|
+
options.directory ??
|
|
91
|
+
(interactive
|
|
92
|
+
? await promptText("Destination directory", {
|
|
93
|
+
defaultValue: name,
|
|
94
|
+
validate: async (value) => {
|
|
95
|
+
try {
|
|
96
|
+
await requireNewDirectory(resolve(value));
|
|
97
|
+
} catch (error) {
|
|
98
|
+
return error.message;
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
})
|
|
102
|
+
: name);
|
|
103
|
+
const directory = resolve(destination);
|
|
81
104
|
await requireNewDirectory(directory);
|
|
82
105
|
const access = await request(connection, "/access");
|
|
83
|
-
|
|
106
|
+
let organization = connection.organization || access.organization;
|
|
107
|
+
if (!organization && interactive) {
|
|
108
|
+
const organizations = [
|
|
109
|
+
...new Set(
|
|
110
|
+
access.projects.map((item) => item.organization).filter(Boolean),
|
|
111
|
+
),
|
|
112
|
+
];
|
|
113
|
+
organization = organizations.length
|
|
114
|
+
? await promptSelect(
|
|
115
|
+
"Choose an organization",
|
|
116
|
+
organizations.map((id) => ({ label: id, value: id })),
|
|
117
|
+
)
|
|
118
|
+
: await promptText("Organization ID");
|
|
119
|
+
}
|
|
84
120
|
if (!organization)
|
|
85
|
-
throw new Error("Select an organization with --organization.");
|
|
121
|
+
throw new Error("Select an organization with --organization ID.");
|
|
86
122
|
const scoped = {
|
|
87
123
|
...connection,
|
|
88
124
|
organization,
|
|
@@ -101,13 +137,31 @@ export function registerHostingCommands(program) {
|
|
|
101
137
|
.description(
|
|
102
138
|
"Recover or clone a hosted campaign with Domino Git authentication",
|
|
103
139
|
)
|
|
104
|
-
.argument(
|
|
140
|
+
.argument(
|
|
141
|
+
"[project]",
|
|
142
|
+
"Project identifier (choose interactively if omitted)",
|
|
143
|
+
)
|
|
105
144
|
.option("--directory <path>", "Local directory (defaults to project name)")
|
|
106
145
|
.action(
|
|
107
146
|
action(async ({ connection, options }, project) => {
|
|
108
|
-
const scoped = {
|
|
147
|
+
const scoped = {
|
|
148
|
+
...connection,
|
|
149
|
+
project: project ?? connection.project,
|
|
150
|
+
environment: "test",
|
|
151
|
+
};
|
|
152
|
+
if (!scoped.project) {
|
|
153
|
+
const access = await request(scoped, "/access");
|
|
154
|
+
const selected = await selectProject(access, scoped, options);
|
|
155
|
+
scoped.project = selected.project;
|
|
156
|
+
scoped.organization =
|
|
157
|
+
scoped.organization ?? selected.organization ?? access.organization;
|
|
158
|
+
}
|
|
109
159
|
const repository = await request(scoped, "/repository");
|
|
110
|
-
return checkout(
|
|
160
|
+
return checkout(
|
|
161
|
+
scoped,
|
|
162
|
+
repository,
|
|
163
|
+
options.directory ?? scoped.project,
|
|
164
|
+
);
|
|
111
165
|
}),
|
|
112
166
|
);
|
|
113
167
|
}
|
package/cli/commands/inspect.mjs
CHANGED
|
@@ -16,7 +16,11 @@ export function registerInspectCommands(program) {
|
|
|
16
16
|
program
|
|
17
17
|
.command("status")
|
|
18
18
|
.description("Show the project overview")
|
|
19
|
-
.action(
|
|
19
|
+
.action(
|
|
20
|
+
action(({ connection }) => request(connection, "/overview"), {
|
|
21
|
+
projectScope: true,
|
|
22
|
+
}),
|
|
23
|
+
);
|
|
20
24
|
|
|
21
25
|
for (const [name, description] of [
|
|
22
26
|
["quests", "List current quests"],
|
|
@@ -34,14 +38,17 @@ export function registerInspectCommands(program) {
|
|
|
34
38
|
.option("--filter <value>", "Filter the collection")
|
|
35
39
|
.option("--q <text>", "Search the collection")
|
|
36
40
|
.action(
|
|
37
|
-
action(
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
41
|
+
action(
|
|
42
|
+
({ connection, options }) => {
|
|
43
|
+
const query = new URLSearchParams();
|
|
44
|
+
for (const key of ["limit", "offset", "filter", "q"]) {
|
|
45
|
+
if (options[key] !== undefined)
|
|
46
|
+
query.set(key, String(options[key]));
|
|
47
|
+
}
|
|
48
|
+
return request(connection, `/${name}?${query}`);
|
|
49
|
+
},
|
|
50
|
+
{ projectScope: true },
|
|
51
|
+
),
|
|
45
52
|
);
|
|
46
53
|
}
|
|
47
54
|
}
|
package/cli/commands/project.mjs
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
import { writeFile } from "node:fs/promises";
|
|
1
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
2
|
+
import { dirname } from "node:path";
|
|
2
3
|
import { action } from "../runtime.mjs";
|
|
4
|
+
import { confirmAction } from "../prompts.mjs";
|
|
5
|
+
import { selectProject } from "../select-project.mjs";
|
|
3
6
|
import { request } from "../connection.mjs";
|
|
4
7
|
import { initialize, bundleProject } from "../project.mjs";
|
|
5
8
|
|
|
@@ -63,7 +66,17 @@ async function build({ project, connection, options }, deploy) {
|
|
|
63
66
|
throw new Error(
|
|
64
67
|
"Live publication requires Console approval. Run deploy --dry-run --out deployment.json, then import the project deployment in Console.",
|
|
65
68
|
);
|
|
66
|
-
|
|
69
|
+
const confirmPublication = (detail) =>
|
|
70
|
+
confirmAction(
|
|
71
|
+
options,
|
|
72
|
+
`Publish to ${connection.organization ?? "current organization"}/${connection.project} in ${connection.environment ?? "test"}.
|
|
73
|
+
API: ${connection.apiUrl}
|
|
74
|
+
${detail}`,
|
|
75
|
+
);
|
|
76
|
+
if (options.resume) {
|
|
77
|
+
await confirmPublication(`Resume deployment ${options.resume}.`);
|
|
78
|
+
return publish(connection, options.resume);
|
|
79
|
+
}
|
|
67
80
|
const payload = await bundleProject(project);
|
|
68
81
|
if (deploy) {
|
|
69
82
|
const preview = await request(
|
|
@@ -73,9 +86,13 @@ async function build({ project, connection, options }, deploy) {
|
|
|
73
86
|
payload,
|
|
74
87
|
);
|
|
75
88
|
if (options.preview) return { status: "preview", ...summary(preview) };
|
|
89
|
+
await confirmPublication(
|
|
90
|
+
`Deployment ${preview.id}: ${preview.releases.length} quests, ${preview.types.length} types, ${preview.collections.length} collections, ${preview.leaderboards.length} leaderboards, ${preview.referrals.length} referral programs.`,
|
|
91
|
+
);
|
|
76
92
|
return publish(connection, preview.id);
|
|
77
93
|
}
|
|
78
94
|
if (options.out) {
|
|
95
|
+
await mkdir(dirname(options.out), { recursive: true });
|
|
79
96
|
await writeFile(options.out, JSON.stringify(payload, null, 2) + "\n");
|
|
80
97
|
return {
|
|
81
98
|
file: options.out,
|
|
@@ -102,21 +119,11 @@ export function registerProjectCommands(program) {
|
|
|
102
119
|
.action(
|
|
103
120
|
action(async ({ connection, options }, directory) => {
|
|
104
121
|
const access = await request(connection, "/access");
|
|
105
|
-
const selected = connection
|
|
106
|
-
? access.projects.find((item) => item.project === connection.project)
|
|
107
|
-
: access.projects.length === 1
|
|
108
|
-
? access.projects[0]
|
|
109
|
-
: null;
|
|
110
|
-
if (!selected)
|
|
111
|
-
throw new Error(
|
|
112
|
-
"Select an accessible project with --project ID. Run domino whoami to list projects; create projects in Console.",
|
|
113
|
-
);
|
|
122
|
+
const selected = await selectProject(access, connection, options);
|
|
114
123
|
const environment = connection.environment ?? "test";
|
|
115
|
-
if (!selected.environments.includes(environment))
|
|
116
|
-
throw new Error(`No access to ${environment} for this project.`);
|
|
117
124
|
return initialize(directory, {
|
|
118
125
|
...connection,
|
|
119
|
-
organization: access.organization,
|
|
126
|
+
organization: selected.organization ?? access.organization,
|
|
120
127
|
project: selected.project,
|
|
121
128
|
environment,
|
|
122
129
|
quests: (options.entry ?? []).map((entry) => ({
|
|
@@ -144,6 +151,7 @@ export function registerProjectCommands(program) {
|
|
|
144
151
|
.description(
|
|
145
152
|
"Atomically deploy quests, reusable types, and collection slots, preserving Console-owned settings and placement",
|
|
146
153
|
)
|
|
154
|
+
.option("--yes", "Skip the terminal publication confirmation")
|
|
147
155
|
.option(
|
|
148
156
|
"--dry-run",
|
|
149
157
|
"Build locally only; does not preview Relay custom values",
|
package/cli/commands/staging.mjs
CHANGED
|
@@ -2,6 +2,8 @@ import { dirname } from "node:path";
|
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
3
|
import { action } from "../runtime.mjs";
|
|
4
4
|
import { request } from "../connection.mjs";
|
|
5
|
+
import { canPrompt, promptSelect, confirmAction } from "../prompts.mjs";
|
|
6
|
+
import { projectConnection } from "../select-project.mjs";
|
|
5
7
|
import { runGit } from "../git.mjs";
|
|
6
8
|
|
|
7
9
|
export function registerStagingCommands(program) {
|
|
@@ -15,21 +17,25 @@ export function registerStagingCommands(program) {
|
|
|
15
17
|
"--request-id <uuid>",
|
|
16
18
|
"Resume a previously submitted build request",
|
|
17
19
|
)
|
|
20
|
+
.option("--yes", "Skip the terminal confirmation")
|
|
18
21
|
.option("--no-wait", "Return the build ID without waiting")
|
|
19
22
|
.action(
|
|
20
23
|
action(async ({ connection, project, options }) => {
|
|
21
24
|
if (!project)
|
|
22
25
|
throw new Error("Run stage inside a hosted campaign checkout.");
|
|
23
|
-
if (project.config.app
|
|
26
|
+
if (!project.config.app)
|
|
24
27
|
throw new Error(
|
|
25
|
-
"
|
|
28
|
+
"Configure app.kind as static or worker in relay.json before staging.",
|
|
26
29
|
);
|
|
27
30
|
const scoped = { ...connection, environment: "test" };
|
|
28
31
|
const preflight = await request(scoped, "/preflight");
|
|
29
32
|
if (preflight.staging !== "configured")
|
|
30
33
|
throw new Error(
|
|
31
34
|
preflight.checks
|
|
32
|
-
.filter(
|
|
35
|
+
.filter(
|
|
36
|
+
(check) =>
|
|
37
|
+
check.id !== "hosted-test-auth" && check.status === "error",
|
|
38
|
+
)
|
|
33
39
|
.map((check) => check.message)
|
|
34
40
|
.join("\n"),
|
|
35
41
|
);
|
|
@@ -45,6 +51,12 @@ export function registerStagingCommands(program) {
|
|
|
45
51
|
options.commit ?? (await runGit(["rev-parse", "HEAD"], { cwd }));
|
|
46
52
|
if (!/^[a-f0-9]{40}$/.test(commit))
|
|
47
53
|
throw new Error("Use a full 40-character commit SHA.");
|
|
54
|
+
await confirmAction(
|
|
55
|
+
options,
|
|
56
|
+
`Stage commit ${commit} for ${scoped.organization ?? "current organization"}/${scoped.project} in test.
|
|
57
|
+
API: ${scoped.apiUrl}
|
|
58
|
+
This builds the pushed commit and updates its staging preview.`,
|
|
59
|
+
);
|
|
48
60
|
const requestId = options.requestId ?? randomUUID();
|
|
49
61
|
process.stderr.write(`Build request ${requestId}\n`);
|
|
50
62
|
let build;
|
|
@@ -81,18 +93,41 @@ export function registerStagingCommands(program) {
|
|
|
81
93
|
.command("builds")
|
|
82
94
|
.description("List recent cloud builds for this campaign")
|
|
83
95
|
.action(
|
|
84
|
-
action(({ connection }) =>
|
|
85
|
-
|
|
86
|
-
|
|
96
|
+
action(({ connection }) => request(connection, "/builds"), {
|
|
97
|
+
projectScope: true,
|
|
98
|
+
environment: "test",
|
|
99
|
+
}),
|
|
87
100
|
);
|
|
88
101
|
program
|
|
89
102
|
.command("logs")
|
|
90
103
|
.description("Read retained cloud build logs")
|
|
91
|
-
.argument("
|
|
104
|
+
.argument("[id]", "Build ID (choose a recent build if omitted)")
|
|
92
105
|
.action(
|
|
93
|
-
action(({ connection }, id) => {
|
|
94
|
-
if (
|
|
106
|
+
action(async ({ connection, options }, id) => {
|
|
107
|
+
if (!id && !canPrompt(options))
|
|
108
|
+
throw new Error(
|
|
109
|
+
"Provide a build ID: domino logs ID. Run domino builds to list recent builds.",
|
|
110
|
+
);
|
|
111
|
+
if (id && !/^[a-f0-9-]{36}$/.test(id))
|
|
95
112
|
throw new Error("Expected a build UUID.");
|
|
113
|
+
connection = await projectConnection(
|
|
114
|
+
{ ...connection, environment: "test" },
|
|
115
|
+
options,
|
|
116
|
+
);
|
|
117
|
+
if (!id) {
|
|
118
|
+
const builds = await request(connection, "/builds");
|
|
119
|
+
if (!builds.length)
|
|
120
|
+
throw new Error(
|
|
121
|
+
"No builds yet. Push a commit and run domino stage first.",
|
|
122
|
+
);
|
|
123
|
+
id = await promptSelect(
|
|
124
|
+
"Choose a build",
|
|
125
|
+
builds.map((build) => ({
|
|
126
|
+
label: `${build.phase} • ${new Date(build.createdAt).toISOString()} • ${build.commit.slice(0, 8)} • ${build.id}${build.current ? " • current" : ""}`,
|
|
127
|
+
value: build.id,
|
|
128
|
+
})),
|
|
129
|
+
);
|
|
130
|
+
}
|
|
96
131
|
return request(
|
|
97
132
|
{ ...connection, environment: "test" },
|
|
98
133
|
`/builds/${id}/logs`,
|
package/cli/connected-dev.mjs
CHANGED
|
@@ -22,7 +22,7 @@ export async function connectedDev(project, connection, options) {
|
|
|
22
22
|
const preflight = await request(connection, "/preflight");
|
|
23
23
|
if (preflight.development?.status !== "configured")
|
|
24
24
|
throw new Error(
|
|
25
|
-
"Hosted development is not configured. Run domino doctor --remote --json; a Domino operator must enable
|
|
25
|
+
"Hosted development is not configured. Run domino doctor --remote --json; a Domino operator must enable preview hosting.",
|
|
26
26
|
);
|
|
27
27
|
const root = dirname(project.path);
|
|
28
28
|
const connector = await previewConnector(options.cloudflared);
|
|
@@ -126,6 +126,7 @@ export async function connectedDev(project, connection, options) {
|
|
|
126
126
|
session = await request(connection, "/development/session", "POST", {
|
|
127
127
|
...lease,
|
|
128
128
|
port,
|
|
129
|
+
routing: project.config.app.routing,
|
|
129
130
|
});
|
|
130
131
|
const sync = developmentSync(connection, project.path);
|
|
131
132
|
await sync();
|
|
@@ -143,6 +144,7 @@ export async function connectedDev(project, connection, options) {
|
|
|
143
144
|
start("pnpm", ["run", project.config.app.devScript], {
|
|
144
145
|
DOMINO_APP_PORT: String(port),
|
|
145
146
|
DOMINO_PREVIEW_ORIGIN: session.url,
|
|
147
|
+
DOMINO_PARTICIPANT_API: connection.apiUrl,
|
|
146
148
|
});
|
|
147
149
|
let appReady = false;
|
|
148
150
|
for (let attempt = 0; attempt < 150 && !stopping; attempt++) {
|
|
@@ -190,7 +192,7 @@ export async function connectedDev(project, connection, options) {
|
|
|
190
192
|
)
|
|
191
193
|
);
|
|
192
194
|
process.stderr.write(
|
|
193
|
-
`Preview: ${session.url}\
|
|
195
|
+
`Preview: ${session.url}\nKeep this terminal running. Use domino stage for a preview that stays online.\n`,
|
|
194
196
|
);
|
|
195
197
|
let renewed = Date.now();
|
|
196
198
|
while (!stopping) {
|
package/cli/dev.mjs
CHANGED
|
@@ -113,7 +113,7 @@ export async function dev(project, options) {
|
|
|
113
113
|
const child = spawn(command, args, {
|
|
114
114
|
cwd: root,
|
|
115
115
|
env: { ...process.env, ...extraEnv },
|
|
116
|
-
stdio: "inherit",
|
|
116
|
+
stdio: ["inherit", 2, 2],
|
|
117
117
|
detached: process.platform !== "win32",
|
|
118
118
|
});
|
|
119
119
|
children.push(child);
|