@nurix/nustack 0.10.0-dev.12 → 0.10.0-dev.14
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/commands/bootstrap.js +4 -4
- package/dist/commands/docs.js +13 -13
- package/dist/commands/doctor.js +13 -13
- package/dist/commands/handoff.js +8 -8
- package/dist/commands/init.js +43 -43
- package/dist/commands/{workspace-sync.js → project-sync.js} +5 -5
- package/dist/commands/{workspace.js → project.js} +133 -133
- package/dist/commands/review.js +5 -5
- package/dist/commands/search.js +7 -7
- package/dist/commands/sessions.js +1 -1
- package/dist/commands/sync.js +10 -10
- package/dist/commands/toolkit.js +19 -19
- package/dist/index.js +29 -29
- package/dist/lib/etna/profile.js +9 -9
- package/dist/lib/etna/reconcile.js +11 -11
- package/dist/lib/git-sync/core/engine.js +4 -4
- package/dist/lib/git-sync/inbound/apply_commit.js +3 -3
- package/dist/lib/git-sync/inbound/approval.js +1 -1
- package/dist/lib/git-sync/inbound/download.js +8 -8
- package/dist/lib/git-sync/inbound/event_sync.js +13 -13
- package/dist/lib/git-sync/inbound/notification.js +1 -1
- package/dist/lib/git-sync/outbound/extraction.js +4 -4
- package/dist/lib/git-sync/outbound/history_decision.js +2 -2
- package/dist/lib/git-sync/outbound/publisher.js +2 -2
- package/dist/lib/git-sync/outbound/queue.js +3 -3
- package/dist/lib/git-sync/outbound/upload_client.js +4 -4
- package/dist/lib/git-sync/whitelist/index.js +1 -1
- package/dist/lib/git-sync/whitelist/types.js +1 -1
- package/dist/lib/git-sync/whitelist/validation.js +5 -5
- package/dist/lib/lanes/supervisor.js +8 -8
- package/dist/lib/local_store/accepted_uploads.js +5 -5
- package/dist/lib/local_store/bindings.js +11 -11
- package/dist/lib/local_store/index.js +1 -1
- package/dist/lib/local_store/migrations.js +13 -0
- package/dist/lib/local_store/project_events_cache.js +54 -0
- package/dist/lib/nustack_client.js +45 -45
- package/dist/lib/onboarding/attach_existing_git.js +7 -7
- package/dist/lib/onboarding/binding.js +11 -11
- package/dist/lib/onboarding/folder_state.js +10 -10
- package/dist/lib/onboarding/handoff_apply.js +2 -2
- package/dist/lib/onboarding/init_fresh_folder.js +18 -18
- package/dist/lib/onboarding/manifest_writer.js +31 -31
- package/dist/lib/onboarding/web_first_attach.js +12 -12
- package/dist/lib/{workspace_context.js → project_context.js} +24 -24
- package/dist/lib/review/client.js +1 -1
- package/dist/lib/session_telemetry.js +2 -2
- package/dist/lib/sessions/client.js +8 -8
- package/dist/lib/sessions/outsideSessions.js +3 -3
- package/dist/lib/telemetry/config_renderer.js +3 -3
- package/dist/lib/telemetry/health_report.js +1 -1
- package/package.json +3 -3
- package/dist/lib/local_store/workspace_events_cache.js +0 -54
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import * as p from "@clack/prompts";
|
|
4
|
-
import {
|
|
4
|
+
import { createProjectViaAsk, getProject, listProjects, listOrganizations, publishProject, resolveNuStackUrl, unpublishProject, } from "../lib/nustack_client.js";
|
|
5
5
|
import { ensureAccessToken } from "../lib/auth_grant.js";
|
|
6
6
|
import { ensureAuthWithRecovery, lastAuthFailure } from "../lib/auth_recovery.js";
|
|
7
|
-
import {
|
|
7
|
+
import { resolveProjectId, resolveOrganizationId } from "../lib/project_context.js";
|
|
8
8
|
import { readActiveOrganizationContext } from "./organization.js";
|
|
9
9
|
import { confirmOrFallback, isInteractive } from "../lib/interactive.js";
|
|
10
10
|
import { launchBrowser } from "../lib/browser_opener.js";
|
|
@@ -13,7 +13,7 @@ import { openLocalStore } from "../lib/local_store/database.js";
|
|
|
13
13
|
import { readDeviceState } from "../lib/local_store/device_state.js";
|
|
14
14
|
import { getBindingByRepositoryRoot, listBindings } from "../lib/local_store/bindings.js";
|
|
15
15
|
import { resolveRepository } from "../lib/git-sync/core/repository.js";
|
|
16
|
-
import {
|
|
16
|
+
import { readProjectManifests } from "../lib/onboarding/manifest_writer.js";
|
|
17
17
|
import { hasManifest, scaffoldManifest } from "../lib/manifest.js";
|
|
18
18
|
import { toolkitCompatibility } from "../lib/etna/reconcile.js";
|
|
19
19
|
import { attachExistingGit } from "../lib/onboarding/attach_existing_git.js";
|
|
@@ -37,96 +37,96 @@ async function requireAuth(command, json, globals) {
|
|
|
37
37
|
}
|
|
38
38
|
return { baseUrl, accessToken: deviceAuth.accessToken };
|
|
39
39
|
}
|
|
40
|
-
async function
|
|
40
|
+
async function resolveOrganizationForProject(auth, options) {
|
|
41
41
|
const memberships = await listOrganizations(auth.baseUrl, auth.accessToken);
|
|
42
42
|
const views = memberships.map((m) => ({ id: m.organization.id, slug: m.organization.slug }));
|
|
43
43
|
const active = await readActiveOrganizationContext();
|
|
44
44
|
const resolution = resolveOrganizationId({ organization: options.organization }, active, views);
|
|
45
45
|
return resolution.ok ? { ok: true, organizationId: resolution.organizationId } : { ok: false, message: resolution.message };
|
|
46
46
|
}
|
|
47
|
-
export async function
|
|
47
|
+
export async function runProjectList(options, globals) {
|
|
48
48
|
const json = options.json === true;
|
|
49
|
-
const auth = await requireAuth("
|
|
49
|
+
const auth = await requireAuth("project list", json, globals);
|
|
50
50
|
if (!auth)
|
|
51
51
|
return;
|
|
52
|
-
const ws = await
|
|
52
|
+
const ws = await resolveOrganizationForProject(auth, options);
|
|
53
53
|
if (!ws.ok) {
|
|
54
|
-
emitFail("
|
|
54
|
+
emitFail("project list", json, "ORGANIZATION_CONTEXT_MISSING", ws.message);
|
|
55
55
|
return;
|
|
56
56
|
}
|
|
57
|
-
const
|
|
57
|
+
const projects = await listProjects(auth.baseUrl, auth.accessToken, ws.organizationId);
|
|
58
58
|
if (json) {
|
|
59
|
-
printJsonSuccess("
|
|
59
|
+
printJsonSuccess("project list", { organizationId: ws.organizationId, projects });
|
|
60
60
|
return;
|
|
61
61
|
}
|
|
62
|
-
if (
|
|
63
|
-
console.log("No
|
|
62
|
+
if (projects.length === 0) {
|
|
63
|
+
console.log("No Projects in this Organization. Create one with `nustack project create`.");
|
|
64
64
|
return;
|
|
65
65
|
}
|
|
66
|
-
for (const
|
|
67
|
-
console.log(`${
|
|
66
|
+
for (const project of projects) {
|
|
67
|
+
console.log(`${project.id.padEnd(24)} ${project.status.padEnd(12)} ${project.publicationState.padEnd(12)} ${project.label}`);
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
|
-
export async function
|
|
70
|
+
export async function runProjectCreate(options, globals) {
|
|
71
71
|
const json = options.json === true;
|
|
72
|
-
const auth = await requireAuth("
|
|
72
|
+
const auth = await requireAuth("project create", json, globals);
|
|
73
73
|
if (!auth)
|
|
74
74
|
return;
|
|
75
|
-
const ws = await
|
|
75
|
+
const ws = await resolveOrganizationForProject(auth, options);
|
|
76
76
|
if (!ws.ok) {
|
|
77
|
-
emitFail("
|
|
77
|
+
emitFail("project create", json, "ORGANIZATION_CONTEXT_MISSING", ws.message);
|
|
78
78
|
return;
|
|
79
79
|
}
|
|
80
80
|
const gathered = await gatherCreatePrompt(options, json);
|
|
81
81
|
if (!gathered.ok) {
|
|
82
|
-
emitFail("
|
|
82
|
+
emitFail("project create", json, "VALIDATION_FAILED", gathered.message);
|
|
83
83
|
return;
|
|
84
84
|
}
|
|
85
|
-
const
|
|
86
|
-
if (!
|
|
85
|
+
const project = await createProjectFromPrompt(auth, ws.organizationId, gathered.prompt, "project create", json);
|
|
86
|
+
if (!project)
|
|
87
87
|
return;
|
|
88
88
|
if (json) {
|
|
89
|
-
printJsonSuccess("
|
|
89
|
+
printJsonSuccess("project create", { project, organizationId: ws.organizationId });
|
|
90
90
|
return;
|
|
91
91
|
}
|
|
92
|
-
console.log(`Created
|
|
93
|
-
console.log(`Attach a local folder with \`nustack
|
|
94
|
-
if (isInteractive() && (await confirmOrFallback("Attach the current folder to this
|
|
92
|
+
console.log(`Created Project ${project.label} (${project.id}), status ${project.status}.`);
|
|
93
|
+
console.log(`Attach a local folder with \`nustack project attach --project ${project.id}\`.`);
|
|
94
|
+
if (isInteractive() && (await confirmOrFallback("Attach the current folder to this Project now?", false))) {
|
|
95
95
|
const store = await openLocalStore();
|
|
96
96
|
try {
|
|
97
97
|
const ctx = buildOnboardingContext(auth, store, json, options.yes === true);
|
|
98
98
|
const outcome = await webFirstAttach(ctx, {
|
|
99
|
-
|
|
99
|
+
projectId: project.id,
|
|
100
100
|
organizationId: ws.organizationId,
|
|
101
101
|
confirmPopulated: options.yes === true,
|
|
102
102
|
});
|
|
103
|
-
reportWebFirst("
|
|
103
|
+
reportWebFirst("project create", outcome, json);
|
|
104
104
|
}
|
|
105
105
|
finally {
|
|
106
106
|
store.close();
|
|
107
107
|
}
|
|
108
108
|
}
|
|
109
109
|
}
|
|
110
|
-
export async function
|
|
110
|
+
export async function runProjectAttach(options, globals) {
|
|
111
111
|
const json = options.json === true;
|
|
112
|
-
const auth = await requireAuth("
|
|
112
|
+
const auth = await requireAuth("project attach", json, globals);
|
|
113
113
|
if (!auth)
|
|
114
114
|
return;
|
|
115
115
|
const store = await openLocalStore();
|
|
116
116
|
try {
|
|
117
117
|
const ctx = buildOnboardingContext(auth, store, json, options.yes === true);
|
|
118
|
-
if (options.
|
|
119
|
-
const ws = await
|
|
118
|
+
if (options.project) {
|
|
119
|
+
const ws = await resolveOrganizationForProject(auth, options);
|
|
120
120
|
if (!ws.ok) {
|
|
121
|
-
emitFail("
|
|
121
|
+
emitFail("project attach", json, "ORGANIZATION_CONTEXT_MISSING", ws.message);
|
|
122
122
|
return;
|
|
123
123
|
}
|
|
124
124
|
const outcome = await webFirstAttach(ctx, {
|
|
125
|
-
|
|
125
|
+
projectId: options.project,
|
|
126
126
|
organizationId: ws.organizationId,
|
|
127
127
|
confirmPopulated: options.yes === true,
|
|
128
128
|
});
|
|
129
|
-
reportWebFirst("
|
|
129
|
+
reportWebFirst("project attach", outcome, json);
|
|
130
130
|
return;
|
|
131
131
|
}
|
|
132
132
|
let root;
|
|
@@ -134,31 +134,31 @@ export async function runWorkspaceAttach(options, globals) {
|
|
|
134
134
|
root = resolveRepository(ctx.cwd).repositoryRoot;
|
|
135
135
|
}
|
|
136
136
|
catch {
|
|
137
|
-
emitFail("
|
|
137
|
+
emitFail("project attach", json, "NOT_A_WORKTREE", "`nustack project attach` needs a Git worktree — run `nustack init` to establish one, or pass `--project <id>` for a web-created Project.");
|
|
138
138
|
return;
|
|
139
139
|
}
|
|
140
|
-
const resolved = await
|
|
140
|
+
const resolved = await resolveOrCreateProjectForAttach(auth, options, root, json);
|
|
141
141
|
if (!resolved.ok)
|
|
142
142
|
return;
|
|
143
143
|
if (!hasManifest(root))
|
|
144
144
|
await scaffoldManifest(root, deriveSlug(path.basename(root)));
|
|
145
145
|
const outcome = await attachExistingGit(ctx, {
|
|
146
|
-
|
|
146
|
+
projectId: resolved.projectId,
|
|
147
147
|
organizationId: resolved.organizationId,
|
|
148
148
|
confirmRebind: options.yes === true,
|
|
149
149
|
});
|
|
150
|
-
reportAttach("
|
|
150
|
+
reportAttach("project attach", outcome, json);
|
|
151
151
|
}
|
|
152
152
|
finally {
|
|
153
153
|
store.close();
|
|
154
154
|
}
|
|
155
155
|
}
|
|
156
|
-
export async function
|
|
156
|
+
export async function runProjectStatus(options, globals) {
|
|
157
157
|
const json = options.json === true;
|
|
158
158
|
const cwd = process.cwd();
|
|
159
|
-
const
|
|
160
|
-
if (!
|
|
161
|
-
emitFail("
|
|
159
|
+
const project = await resolveProjectId(cwd, { project: options.project });
|
|
160
|
+
if (!project.ok) {
|
|
161
|
+
emitFail("project status", json, "PROJECT_CONTEXT_MISSING", project.message);
|
|
162
162
|
return;
|
|
163
163
|
}
|
|
164
164
|
const baseUrl = resolveNuStackUrl(globals);
|
|
@@ -166,44 +166,44 @@ export async function runWorkspaceStatus(options, globals) {
|
|
|
166
166
|
const store = await openLocalStore();
|
|
167
167
|
let binding = null;
|
|
168
168
|
try {
|
|
169
|
-
binding =
|
|
169
|
+
binding = findBindingForProject(store, project.projectId, cwd);
|
|
170
170
|
}
|
|
171
171
|
finally {
|
|
172
172
|
store.close();
|
|
173
173
|
}
|
|
174
|
-
let
|
|
174
|
+
let cloudProject = null;
|
|
175
175
|
let cloud = null;
|
|
176
176
|
if (auth) {
|
|
177
177
|
try {
|
|
178
|
-
|
|
179
|
-
if (
|
|
178
|
+
cloudProject = await getProject(baseUrl, auth.accessToken, project.projectId);
|
|
179
|
+
if (cloudProject.toolkit && cloudProject.toolkit.version && cloudProject.toolkit.rendererVersion && cloudProject.toolkit.placementMapVersion) {
|
|
180
180
|
cloud = {
|
|
181
|
-
name:
|
|
182
|
-
version:
|
|
183
|
-
rendererVersion:
|
|
184
|
-
placementMapVersion:
|
|
181
|
+
name: cloudProject.toolkit.name,
|
|
182
|
+
version: cloudProject.toolkit.version,
|
|
183
|
+
rendererVersion: cloudProject.toolkit.rendererVersion,
|
|
184
|
+
placementMapVersion: cloudProject.toolkit.placementMapVersion,
|
|
185
185
|
catalogVersionId: null,
|
|
186
186
|
};
|
|
187
187
|
}
|
|
188
188
|
}
|
|
189
189
|
catch {
|
|
190
|
-
|
|
190
|
+
cloudProject = null;
|
|
191
191
|
}
|
|
192
192
|
}
|
|
193
193
|
const compatibility = toolkitCompatibility(binding ?? { etnaProfileJson: null }, cloud);
|
|
194
194
|
if (json) {
|
|
195
|
-
printJsonSuccess("
|
|
196
|
-
|
|
197
|
-
cloudReachable:
|
|
198
|
-
cloud:
|
|
199
|
-
? { label:
|
|
195
|
+
printJsonSuccess("project status", {
|
|
196
|
+
projectId: project.projectId,
|
|
197
|
+
cloudReachable: cloudProject !== null,
|
|
198
|
+
cloud: cloudProject
|
|
199
|
+
? { label: cloudProject.label, status: cloudProject.status, publicationState: cloudProject.publicationState, initStatus: cloudProject.initStatus }
|
|
200
200
|
: null,
|
|
201
201
|
binding: binding
|
|
202
202
|
? {
|
|
203
203
|
id: binding.id,
|
|
204
204
|
boundBranch: binding.boundBranch,
|
|
205
205
|
lastProcessedCommitId: binding.lastProcessedCommitId,
|
|
206
|
-
|
|
206
|
+
lastAppliedProjectSequence: binding.lastAppliedProjectSequence,
|
|
207
207
|
status: binding.status,
|
|
208
208
|
}
|
|
209
209
|
: null,
|
|
@@ -211,71 +211,71 @@ export async function runWorkspaceStatus(options, globals) {
|
|
|
211
211
|
});
|
|
212
212
|
return;
|
|
213
213
|
}
|
|
214
|
-
console.log(`
|
|
215
|
-
console.log(` cloud: ${
|
|
214
|
+
console.log(`Project ${project.projectId}`);
|
|
215
|
+
console.log(` cloud: ${cloudProject ? `${cloudProject.label} — ${cloudProject.status} / ${cloudProject.publicationState}` : "(unreachable — local-only view)"}`);
|
|
216
216
|
if (binding) {
|
|
217
217
|
console.log(` binding: ${binding.id} on ${binding.boundBranch} (${binding.status})`);
|
|
218
|
-
console.log(` cursor: processed=${binding.lastProcessedCommitId?.slice(0, 12) ?? "—"} applied@${binding.
|
|
218
|
+
console.log(` cursor: processed=${binding.lastProcessedCommitId?.slice(0, 12) ?? "—"} applied@${binding.lastAppliedProjectSequence}`);
|
|
219
219
|
}
|
|
220
220
|
else {
|
|
221
221
|
console.log(" binding: (this folder is not bound on this device)");
|
|
222
222
|
}
|
|
223
223
|
console.log(` toolkit: ${compatibility.blocksWrites ? `BLOCKED — ${compatibility.reason}` : "clear (projection and sync writes allowed)"}`);
|
|
224
224
|
}
|
|
225
|
-
export async function
|
|
225
|
+
export async function runProjectOpen(options, globals) {
|
|
226
226
|
const json = options.json === true;
|
|
227
|
-
const
|
|
228
|
-
if (!
|
|
229
|
-
emitFail("
|
|
227
|
+
const project = await resolveProjectId(process.cwd(), { project: options.project });
|
|
228
|
+
if (!project.ok) {
|
|
229
|
+
emitFail("project open", json, "PROJECT_CONTEXT_MISSING", project.message);
|
|
230
230
|
return;
|
|
231
231
|
}
|
|
232
232
|
const baseUrl = resolveNuStackUrl(globals);
|
|
233
233
|
let url;
|
|
234
234
|
try {
|
|
235
|
-
url = assertSafeBrowserUrl(`${baseUrl}/
|
|
235
|
+
url = assertSafeBrowserUrl(`${baseUrl}/projects/${encodeURIComponent(project.projectId)}`);
|
|
236
236
|
}
|
|
237
237
|
catch (error) {
|
|
238
|
-
emitFail("
|
|
238
|
+
emitFail("project open", json, "UNSAFE_URL", error instanceof Error ? error.message : String(error));
|
|
239
239
|
return;
|
|
240
240
|
}
|
|
241
241
|
if (json) {
|
|
242
|
-
printJsonSuccess("
|
|
242
|
+
printJsonSuccess("project open", { projectId: project.projectId, url });
|
|
243
243
|
return;
|
|
244
244
|
}
|
|
245
245
|
launchBrowser(url);
|
|
246
246
|
console.log(`Opening ${url}`);
|
|
247
247
|
}
|
|
248
|
-
export async function
|
|
249
|
-
return runPublication("publish", "
|
|
248
|
+
export async function runProjectPublish(options, globals) {
|
|
249
|
+
return runPublication("publish", "project publish", options, globals);
|
|
250
250
|
}
|
|
251
|
-
export async function
|
|
252
|
-
return runPublication("unpublish", "
|
|
251
|
+
export async function runProjectUnpublish(options, globals) {
|
|
252
|
+
return runPublication("unpublish", "project unpublish", options, globals);
|
|
253
253
|
}
|
|
254
254
|
async function runPublication(action, command, options, globals) {
|
|
255
255
|
const json = options.json === true;
|
|
256
256
|
const auth = await requireAuth(command, json, globals);
|
|
257
257
|
if (!auth)
|
|
258
258
|
return;
|
|
259
|
-
const
|
|
260
|
-
if (!
|
|
261
|
-
emitFail(command, json, "
|
|
259
|
+
const project = await resolveProjectId(process.cwd(), { project: options.project });
|
|
260
|
+
if (!project.ok) {
|
|
261
|
+
emitFail(command, json, "PROJECT_CONTEXT_MISSING", project.message);
|
|
262
262
|
return;
|
|
263
263
|
}
|
|
264
264
|
try {
|
|
265
265
|
if (action === "publish")
|
|
266
|
-
await
|
|
266
|
+
await publishProject(auth.baseUrl, auth.accessToken, project.projectId, {}, randomUUID());
|
|
267
267
|
else
|
|
268
|
-
await
|
|
268
|
+
await unpublishProject(auth.baseUrl, auth.accessToken, project.projectId, randomUUID());
|
|
269
269
|
}
|
|
270
270
|
catch (error) {
|
|
271
271
|
emitFail(command, json, action === "publish" ? "PUBLISH_FAILED" : "UNPUBLISH_FAILED", error instanceof Error ? error.message : String(error));
|
|
272
272
|
return;
|
|
273
273
|
}
|
|
274
274
|
if (json) {
|
|
275
|
-
printJsonSuccess(command, {
|
|
275
|
+
printJsonSuccess(command, { projectId: project.projectId, publicationState: action === "publish" ? "public" : "private" });
|
|
276
276
|
return;
|
|
277
277
|
}
|
|
278
|
-
console.log(action === "publish" ? `Published
|
|
278
|
+
console.log(action === "publish" ? `Published Project ${project.projectId}.` : `Unpublished Project ${project.projectId}.`);
|
|
279
279
|
}
|
|
280
280
|
function buildOnboardingContext(auth, store, json, yes) {
|
|
281
281
|
const deviceState = readDeviceState(store);
|
|
@@ -297,61 +297,61 @@ async function gatherCreatePrompt(options, json) {
|
|
|
297
297
|
return { ok: true, prompt: flagPrompt };
|
|
298
298
|
const name = options.name?.trim();
|
|
299
299
|
if (name)
|
|
300
|
-
return { ok: true, prompt: `Create a
|
|
300
|
+
return { ok: true, prompt: `Create a project named "${name}".` };
|
|
301
301
|
if (json || !isInteractive()) {
|
|
302
302
|
return { ok: false, message: "A prompt is required — pass `--prompt <text>` (or `--name <name>`)." };
|
|
303
303
|
}
|
|
304
|
-
const answer = await p.text({ message: "What should this
|
|
304
|
+
const answer = await p.text({ message: "What should this project be?" });
|
|
305
305
|
if (p.isCancel(answer) || !String(answer).trim())
|
|
306
306
|
return { ok: false, message: "A prompt is required." };
|
|
307
307
|
return { ok: true, prompt: String(answer).trim() };
|
|
308
308
|
}
|
|
309
|
-
async function
|
|
309
|
+
async function createProjectFromPrompt(auth, organizationId, prompt, command, json) {
|
|
310
310
|
let outcome;
|
|
311
311
|
try {
|
|
312
|
-
outcome = await
|
|
312
|
+
outcome = await createProjectViaAsk(auth.baseUrl, auth.accessToken, organizationId, prompt, {
|
|
313
313
|
onPhase: json ? undefined : (message) => p.log.info(message),
|
|
314
314
|
});
|
|
315
315
|
}
|
|
316
316
|
catch (error) {
|
|
317
|
-
emitFail(command, json, "
|
|
317
|
+
emitFail(command, json, "PROJECT_CREATE_FAILED", error instanceof Error ? error.message : String(error));
|
|
318
318
|
return null;
|
|
319
319
|
}
|
|
320
320
|
if (outcome.generationError && !json) {
|
|
321
321
|
p.log.warn(`The first document did not finish: ${outcome.generationError}`);
|
|
322
322
|
}
|
|
323
|
-
return
|
|
323
|
+
return getProject(auth.baseUrl, auth.accessToken, outcome.projectId);
|
|
324
324
|
}
|
|
325
|
-
async function
|
|
326
|
-
const marker = await
|
|
325
|
+
async function resolveOrCreateProjectForAttach(auth, options, root, json) {
|
|
326
|
+
const marker = await resolveProjectId(root, {});
|
|
327
327
|
if (marker.ok) {
|
|
328
|
-
const manifests = await
|
|
329
|
-
const markerOrganization = manifests.
|
|
328
|
+
const manifests = await readProjectManifests(root);
|
|
329
|
+
const markerOrganization = manifests.projectManifest?.organizationId ?? manifests.serviceProjectBinding?.organizationId ?? null;
|
|
330
330
|
if (markerOrganization)
|
|
331
|
-
return { ok: true,
|
|
332
|
-
const ws = await
|
|
331
|
+
return { ok: true, projectId: marker.projectId, organizationId: markerOrganization };
|
|
332
|
+
const ws = await resolveOrganizationForProject(auth, options);
|
|
333
333
|
if (!ws.ok) {
|
|
334
|
-
emitFail("
|
|
334
|
+
emitFail("project attach", json, "ORGANIZATION_CONTEXT_MISSING", ws.message);
|
|
335
335
|
return { ok: false };
|
|
336
336
|
}
|
|
337
|
-
return { ok: true,
|
|
337
|
+
return { ok: true, projectId: marker.projectId, organizationId: ws.organizationId };
|
|
338
338
|
}
|
|
339
|
-
const ws = await
|
|
339
|
+
const ws = await resolveOrganizationForProject(auth, options);
|
|
340
340
|
if (!ws.ok) {
|
|
341
|
-
emitFail("
|
|
341
|
+
emitFail("project attach", json, "ORGANIZATION_CONTEXT_MISSING", ws.message);
|
|
342
342
|
return { ok: false };
|
|
343
343
|
}
|
|
344
344
|
const gathered = await gatherCreatePrompt(options, json);
|
|
345
345
|
if (!gathered.ok) {
|
|
346
|
-
emitFail("
|
|
346
|
+
emitFail("project attach", json, "PROJECT_CONTEXT_MISSING", `No Project to attach — ${gathered.message} Or pass \`--project <id>\` for a web-created Project.`);
|
|
347
347
|
return { ok: false };
|
|
348
348
|
}
|
|
349
|
-
const
|
|
350
|
-
if (!
|
|
349
|
+
const project = await createProjectFromPrompt(auth, ws.organizationId, gathered.prompt, "project attach", json);
|
|
350
|
+
if (!project)
|
|
351
351
|
return { ok: false };
|
|
352
|
-
return { ok: true,
|
|
352
|
+
return { ok: true, projectId: project.id, organizationId: ws.organizationId };
|
|
353
353
|
}
|
|
354
|
-
function
|
|
354
|
+
function findBindingForProject(store, projectId, cwd) {
|
|
355
355
|
try {
|
|
356
356
|
const byRoot = getBindingByRepositoryRoot(store, resolveRepository(cwd).repositoryRoot);
|
|
357
357
|
if (byRoot)
|
|
@@ -359,15 +359,15 @@ function findBindingForWorkspace(store, workspaceId, cwd) {
|
|
|
359
359
|
}
|
|
360
360
|
catch {
|
|
361
361
|
}
|
|
362
|
-
return listBindings(store).find((binding) => binding.
|
|
362
|
+
return listBindings(store).find((binding) => binding.projectId === projectId) ?? null;
|
|
363
363
|
}
|
|
364
364
|
function deriveSlug(value) {
|
|
365
|
-
return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "
|
|
365
|
+
return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "project";
|
|
366
366
|
}
|
|
367
367
|
function toResultJson(result) {
|
|
368
368
|
return {
|
|
369
369
|
flow: result.flow,
|
|
370
|
-
|
|
370
|
+
projectId: result.projectId,
|
|
371
371
|
organizationId: result.organizationId,
|
|
372
372
|
bindingId: result.bindingId,
|
|
373
373
|
boundBranch: result.boundBranch,
|
|
@@ -379,7 +379,7 @@ function toResultJson(result) {
|
|
|
379
379
|
};
|
|
380
380
|
}
|
|
381
381
|
function printOnboardingHuman(result) {
|
|
382
|
-
console.log(`Bound
|
|
382
|
+
console.log(`Bound Project ${result.projectId} on branch ${result.boundBranch} (binding ${result.bindingId})${result.reused ? " [reused]" : ""}.`);
|
|
383
383
|
const installNote = result.install.skipped
|
|
384
384
|
? result.install.reason ?? "no install"
|
|
385
385
|
: result.install.installed
|
|
@@ -407,7 +407,7 @@ function reportWebFirst(command, outcome, json) {
|
|
|
407
407
|
}
|
|
408
408
|
printOnboardingHuman(result);
|
|
409
409
|
if (result.overwriteRequired) {
|
|
410
|
-
console.log(`Divergent target(s) — nothing was changed. Review, then run \`nustack
|
|
410
|
+
console.log(`Divergent target(s) — nothing was changed. Review, then run \`nustack project sync approve ${result.handoffId ?? ""}\`:`);
|
|
411
411
|
for (const entry of result.divergent)
|
|
412
412
|
console.log(` ${entry.path} (${entry.divergence})`);
|
|
413
413
|
}
|
|
@@ -426,54 +426,54 @@ function reportAttach(command, outcome, json) {
|
|
|
426
426
|
}
|
|
427
427
|
printOnboardingHuman(outcome.result);
|
|
428
428
|
}
|
|
429
|
-
export function
|
|
430
|
-
|
|
429
|
+
export function registerProjectCommands(projectCommand, getGlobalOptions) {
|
|
430
|
+
projectCommand
|
|
431
431
|
.command("list")
|
|
432
|
-
.description("List the
|
|
432
|
+
.description("List the Projects in the active (or --organization) Organization")
|
|
433
433
|
.option("--organization <id>", "the Organization to list (defaults to the active Organization)")
|
|
434
434
|
.option("--json", "emit the versioned machine envelope")
|
|
435
|
-
.action(async (options) =>
|
|
436
|
-
|
|
435
|
+
.action(async (options) => runProjectList(options, getGlobalOptions()));
|
|
436
|
+
projectCommand
|
|
437
437
|
.command("create")
|
|
438
|
-
.description("Create a
|
|
438
|
+
.description("Create a Project from a prompt, then optionally attach the current folder")
|
|
439
439
|
.option("--organization <id>", "the owning Organization (defaults to the active Organization)")
|
|
440
|
-
.option("--prompt <text>", "what the
|
|
440
|
+
.option("--prompt <text>", "what the Project should be — the first prompt creates it")
|
|
441
441
|
.option("--name <name>", "shorthand: builds the create prompt from a name")
|
|
442
442
|
.option("--yes", "assume yes for onboarding confirmations")
|
|
443
443
|
.option("--json", "emit the versioned machine envelope")
|
|
444
|
-
.action(async (options) =>
|
|
445
|
-
|
|
444
|
+
.action(async (options) => runProjectCreate(options, getGlobalOptions()));
|
|
445
|
+
projectCommand
|
|
446
446
|
.command("attach")
|
|
447
|
-
.description("Bind the current folder to a
|
|
448
|
-
.option("--
|
|
447
|
+
.description("Bind the current folder to a Project (existing-Git flow, or --project for a web-created Project)")
|
|
448
|
+
.option("--project <id>", "attach to this web-created Project (web-first flow)")
|
|
449
449
|
.option("--organization <id>", "the owning Organization (defaults to the active Organization)")
|
|
450
|
-
.option("--prompt <text>", "the create prompt when creating a
|
|
450
|
+
.option("--prompt <text>", "the create prompt when creating a Project for this worktree")
|
|
451
451
|
.option("--name <name>", "shorthand: builds the create prompt from a name")
|
|
452
452
|
.option("--yes", "confirm populated-folder onboarding and rebind")
|
|
453
453
|
.option("--json", "emit the versioned machine envelope")
|
|
454
|
-
.action(async (options) =>
|
|
455
|
-
|
|
454
|
+
.action(async (options) => runProjectAttach(options, getGlobalOptions()));
|
|
455
|
+
projectCommand
|
|
456
456
|
.command("status")
|
|
457
|
-
.description("Show the cloud
|
|
458
|
-
.option("--
|
|
457
|
+
.description("Show the cloud Project summary merged with the local binding (degraded offline)")
|
|
458
|
+
.option("--project <id>", "the Project to act as (defaults to the bound repository)")
|
|
459
459
|
.option("--json", "emit the versioned machine envelope")
|
|
460
|
-
.action(async (options) =>
|
|
461
|
-
|
|
460
|
+
.action(async (options) => runProjectStatus(options, getGlobalOptions()));
|
|
461
|
+
projectCommand
|
|
462
462
|
.command("open")
|
|
463
|
-
.description("Open the
|
|
464
|
-
.option("--
|
|
463
|
+
.description("Open the Project in the browser (--json prints the URL instead of launching)")
|
|
464
|
+
.option("--project <id>", "the Project to open (defaults to the bound repository)")
|
|
465
465
|
.option("--json", "print the resolved URL as the machine envelope instead of launching")
|
|
466
|
-
.action(async (options) =>
|
|
467
|
-
|
|
466
|
+
.action(async (options) => runProjectOpen(options, getGlobalOptions()));
|
|
467
|
+
projectCommand
|
|
468
468
|
.command("publish")
|
|
469
|
-
.description("Publish the
|
|
470
|
-
.option("--
|
|
469
|
+
.description("Publish the Project (role-gated)")
|
|
470
|
+
.option("--project <id>", "the Project to publish (defaults to the bound repository)")
|
|
471
471
|
.option("--json", "emit the versioned machine envelope")
|
|
472
|
-
.action(async (options) =>
|
|
473
|
-
|
|
472
|
+
.action(async (options) => runProjectPublish(options, getGlobalOptions()));
|
|
473
|
+
projectCommand
|
|
474
474
|
.command("unpublish")
|
|
475
|
-
.description("Return the
|
|
476
|
-
.option("--
|
|
475
|
+
.description("Return the Project to private (role-gated)")
|
|
476
|
+
.option("--project <id>", "the Project to unpublish (defaults to the bound repository)")
|
|
477
477
|
.option("--json", "emit the versioned machine envelope")
|
|
478
|
-
.action(async (options) =>
|
|
478
|
+
.action(async (options) => runProjectUnpublish(options, getGlobalOptions()));
|
|
479
479
|
}
|
package/dist/commands/review.js
CHANGED
|
@@ -117,13 +117,13 @@ export async function runReviewMirror(options, globals) {
|
|
|
117
117
|
const answered = await client.answerReviewItem(id, verdict);
|
|
118
118
|
return mirrorReport(json, "answered", answered.id);
|
|
119
119
|
}
|
|
120
|
-
const
|
|
121
|
-
if (!
|
|
122
|
-
return mirrorReport(json, "skipped", null, "--
|
|
120
|
+
const projectId = options.project?.trim() ?? "";
|
|
121
|
+
if (!projectId)
|
|
122
|
+
return mirrorReport(json, "skipped", null, "--project is required to open a mirrored prompt");
|
|
123
123
|
const subject = readSubject(options.subject);
|
|
124
124
|
if (!subject)
|
|
125
125
|
return mirrorReport(json, "skipped", null, "--subject must be a tool-permission JSON object");
|
|
126
|
-
const created = await client.createReviewItem({
|
|
126
|
+
const created = await client.createReviewItem({ projectId, sessionId, subject });
|
|
127
127
|
return mirrorReport(json, "open", created.id);
|
|
128
128
|
}
|
|
129
129
|
catch (error) {
|
|
@@ -142,7 +142,7 @@ export function registerReviewCommands(reviewCommand, getGlobalOptions) {
|
|
|
142
142
|
.command("mirror")
|
|
143
143
|
.description("Reflect a lane's permission prompt, or its answer, into the Review pane")
|
|
144
144
|
.option("--session <laneId>", "the lane's session id")
|
|
145
|
-
.option("--
|
|
145
|
+
.option("--project <id>", "the Project the lane's folder is bound to (required with --state open)")
|
|
146
146
|
.option("--state <state>", "open or answered")
|
|
147
147
|
.option("--subject <json>", "the tool-permission subject, as JSON (required with --state open)")
|
|
148
148
|
.option("--verdict <verdict>", "allow or deny (required with --state answered)")
|
package/dist/commands/search.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { resolveNuStackUrl,
|
|
1
|
+
import { resolveNuStackUrl, searchProjects, searchCatalogue, } from "../lib/nustack_client.js";
|
|
2
2
|
export async function runSearch(keywords, options, globals) {
|
|
3
3
|
const query = keywords.join(" ").trim();
|
|
4
4
|
if (!query) {
|
|
@@ -12,20 +12,20 @@ export async function runSearch(keywords, options, globals) {
|
|
|
12
12
|
return;
|
|
13
13
|
}
|
|
14
14
|
const baseUrl = resolveNuStackUrl(globals);
|
|
15
|
-
const results = await
|
|
15
|
+
const results = await searchProjects(baseUrl, query, { category: options.category, limit: options.limit });
|
|
16
16
|
if (options.json) {
|
|
17
17
|
console.log(JSON.stringify(results, null, 2));
|
|
18
18
|
return;
|
|
19
19
|
}
|
|
20
20
|
if (results.length === 0) {
|
|
21
|
-
console.log(`No
|
|
21
|
+
console.log(`No projects matched "${query}". Looking for a platform capability? Try \`nustack search services ${query}\`.`);
|
|
22
22
|
return;
|
|
23
23
|
}
|
|
24
24
|
console.log(`${results.length} result${results.length === 1 ? "" : "s"} for "${query}"`);
|
|
25
|
-
for (const
|
|
26
|
-
const category =
|
|
27
|
-
console.log(`${
|
|
28
|
-
const link =
|
|
25
|
+
for (const project of results) {
|
|
26
|
+
const category = project.categories[0] ?? "—";
|
|
27
|
+
console.log(`${project.displayName.padEnd(28)} ${category.padEnd(16)} ${project.tagline ?? ""}`);
|
|
28
|
+
const link = project.links[0];
|
|
29
29
|
if (link)
|
|
30
30
|
console.log(` ${link.url}`);
|
|
31
31
|
}
|
|
@@ -25,7 +25,7 @@ function readTranscripts(value) {
|
|
|
25
25
|
sessionKey,
|
|
26
26
|
provider,
|
|
27
27
|
modifiedMs,
|
|
28
|
-
|
|
28
|
+
projectId: typeof row.projectId === "string" && row.projectId ? row.projectId : null,
|
|
29
29
|
vendorSessionId: typeof row.id === "string" && row.id ? row.id : null,
|
|
30
30
|
...(typeof row.sizeBytes === "number" ? { sizeBytes: row.sizeBytes } : {}),
|
|
31
31
|
});
|