@craftspace/cli 0.5.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/dist/index.js +212 -47
- package/dist/machine.js +51 -17
- package/dist/run.d.ts +2 -1
- package/dist/run.js +2 -2
- package/dist/setup.d.ts +6 -1
- package/dist/setup.js +60 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -18788,6 +18788,18 @@ var AuditActor;
|
|
|
18788
18788
|
AuditActor2["System"] = "system";
|
|
18789
18789
|
AuditActor2["Anonymous"] = "anonymous";
|
|
18790
18790
|
})(AuditActor || (AuditActor = {}));
|
|
18791
|
+
var AuditResource;
|
|
18792
|
+
(function(AuditResource2) {
|
|
18793
|
+
AuditResource2["Members"] = "members";
|
|
18794
|
+
AuditResource2["Connections"] = "connections";
|
|
18795
|
+
AuditResource2["Pages"] = "pages";
|
|
18796
|
+
AuditResource2["Spaces"] = "spaces";
|
|
18797
|
+
AuditResource2["Tables"] = "tables";
|
|
18798
|
+
AuditResource2["Agents"] = "agents";
|
|
18799
|
+
AuditResource2["Automation"] = "automation";
|
|
18800
|
+
AuditResource2["Organization"] = "organization";
|
|
18801
|
+
AuditResource2["Tools"] = "tools";
|
|
18802
|
+
})(AuditResource || (AuditResource = {}));
|
|
18791
18803
|
var AuditTier;
|
|
18792
18804
|
(function(AuditTier2) {
|
|
18793
18805
|
AuditTier2["Critical"] = "critical";
|
|
@@ -18900,7 +18912,8 @@ var AUDIT_RETENTION_DAYS = {
|
|
|
18900
18912
|
};
|
|
18901
18913
|
var AUDIT_GROUPS = [
|
|
18902
18914
|
{
|
|
18903
|
-
|
|
18915
|
+
resource: AuditResource.Members,
|
|
18916
|
+
label: "Members and tokens",
|
|
18904
18917
|
actions: [
|
|
18905
18918
|
AuditAction.AccessDenied,
|
|
18906
18919
|
AuditAction.MemberSignedIn,
|
|
@@ -18914,7 +18927,8 @@ var AUDIT_GROUPS = [
|
|
|
18914
18927
|
]
|
|
18915
18928
|
},
|
|
18916
18929
|
{
|
|
18917
|
-
|
|
18930
|
+
resource: AuditResource.Connections,
|
|
18931
|
+
label: "Connected apps",
|
|
18918
18932
|
actions: [
|
|
18919
18933
|
AuditAction.ConnectionCreated,
|
|
18920
18934
|
AuditAction.ConnectionReconnected,
|
|
@@ -18926,23 +18940,34 @@ var AUDIT_GROUPS = [
|
|
|
18926
18940
|
]
|
|
18927
18941
|
},
|
|
18928
18942
|
{
|
|
18929
|
-
|
|
18943
|
+
resource: AuditResource.Pages,
|
|
18944
|
+
label: "Pages",
|
|
18930
18945
|
actions: [
|
|
18931
18946
|
AuditAction.PageCreated,
|
|
18932
18947
|
AuditAction.PageUpdated,
|
|
18933
18948
|
AuditAction.PageDeleted,
|
|
18934
18949
|
AuditAction.PageMoved,
|
|
18935
18950
|
AuditAction.PageShared,
|
|
18936
|
-
AuditAction.PageUnshared
|
|
18951
|
+
AuditAction.PageUnshared
|
|
18952
|
+
]
|
|
18953
|
+
},
|
|
18954
|
+
{
|
|
18955
|
+
resource: AuditResource.Spaces,
|
|
18956
|
+
label: "Spaces",
|
|
18957
|
+
actions: [
|
|
18937
18958
|
AuditAction.SpaceCreated,
|
|
18938
18959
|
AuditAction.SpaceRenamed,
|
|
18939
18960
|
AuditAction.SpaceDeleted,
|
|
18940
|
-
AuditAction.SpaceAccessChanged
|
|
18941
|
-
AuditAction.TableCreated,
|
|
18942
|
-
AuditAction.TableDeleted
|
|
18961
|
+
AuditAction.SpaceAccessChanged
|
|
18943
18962
|
]
|
|
18944
18963
|
},
|
|
18945
18964
|
{
|
|
18965
|
+
resource: AuditResource.Tables,
|
|
18966
|
+
label: "Tables",
|
|
18967
|
+
actions: [AuditAction.TableCreated, AuditAction.TableDeleted]
|
|
18968
|
+
},
|
|
18969
|
+
{
|
|
18970
|
+
resource: AuditResource.Agents,
|
|
18946
18971
|
label: "Agents and channels",
|
|
18947
18972
|
actions: [
|
|
18948
18973
|
AuditAction.AgentCreated,
|
|
@@ -18953,7 +18978,8 @@ var AUDIT_GROUPS = [
|
|
|
18953
18978
|
]
|
|
18954
18979
|
},
|
|
18955
18980
|
{
|
|
18956
|
-
|
|
18981
|
+
resource: AuditResource.Automation,
|
|
18982
|
+
label: "Workflows and kits",
|
|
18957
18983
|
actions: [
|
|
18958
18984
|
AuditAction.KitInstalled,
|
|
18959
18985
|
AuditAction.KitRemoved,
|
|
@@ -18964,10 +18990,7 @@ var AUDIT_GROUPS = [
|
|
|
18964
18990
|
]
|
|
18965
18991
|
},
|
|
18966
18992
|
{
|
|
18967
|
-
|
|
18968
|
-
actions: [AuditAction.McpToolCall, AuditAction.McpRequest]
|
|
18969
|
-
},
|
|
18970
|
-
{
|
|
18993
|
+
resource: AuditResource.Organization,
|
|
18971
18994
|
label: "Organization",
|
|
18972
18995
|
actions: [
|
|
18973
18996
|
AuditAction.OrganizationRenamed,
|
|
@@ -18975,10 +18998,17 @@ var AUDIT_GROUPS = [
|
|
|
18975
18998
|
AuditAction.OrganizationModelChanged,
|
|
18976
18999
|
AuditAction.OrganizationDeleted
|
|
18977
19000
|
]
|
|
19001
|
+
},
|
|
19002
|
+
{
|
|
19003
|
+
resource: AuditResource.Tools,
|
|
19004
|
+
label: "Tool and MCP calls",
|
|
19005
|
+
actions: [AuditAction.McpToolCall, AuditAction.McpRequest]
|
|
18978
19006
|
}
|
|
18979
19007
|
];
|
|
19008
|
+
var AUDIT_RESOURCES = Object.fromEntries(AUDIT_GROUPS.flatMap((group) => group.actions.map((action) => [action, group.resource])));
|
|
18980
19009
|
var AUDIT_LISTED_ACTIONS = AUDIT_GROUPS.flatMap((group) => group.actions);
|
|
18981
|
-
var
|
|
19010
|
+
var AUDIT_QUIET_ACTIONS = [AuditAction.McpRequest, AuditAction.PageRead, AuditAction.PageWrite];
|
|
19011
|
+
var AUDIT_DEFAULT_ACTIONS = AUDIT_LISTED_ACTIONS.filter((action) => !AUDIT_QUIET_ACTIONS.includes(action));
|
|
18982
19012
|
var AUDIT_LABELS = {
|
|
18983
19013
|
[AuditAction.AccessDenied]: "Denied request",
|
|
18984
19014
|
[AuditAction.MemberSignedIn]: "Signed in",
|
|
@@ -19028,6 +19058,11 @@ var AUDIT_LABELS = {
|
|
|
19028
19058
|
[AuditAction.PageRead]: "Read page",
|
|
19029
19059
|
[AuditAction.PageWrite]: "Wrote page"
|
|
19030
19060
|
};
|
|
19061
|
+
var AuditAppSchema = external_exports.object({
|
|
19062
|
+
kind: external_exports.string(),
|
|
19063
|
+
id: external_exports.string().optional(),
|
|
19064
|
+
name: external_exports.string().optional()
|
|
19065
|
+
});
|
|
19031
19066
|
var AuditTargetSchema = external_exports.object({
|
|
19032
19067
|
type: external_exports.string(),
|
|
19033
19068
|
id: external_exports.string().optional(),
|
|
@@ -19045,6 +19080,8 @@ var AuditEntrySchema = external_exports.object({
|
|
|
19045
19080
|
image: external_exports.string().optional()
|
|
19046
19081
|
}),
|
|
19047
19082
|
target: AuditTargetSchema.optional(),
|
|
19083
|
+
app: AuditAppSchema.optional(),
|
|
19084
|
+
outcome: external_exports.enum(["ok", "error"]).optional(),
|
|
19048
19085
|
data: external_exports.record(external_exports.string(), external_exports.unknown()),
|
|
19049
19086
|
ip: external_exports.string().optional(),
|
|
19050
19087
|
userAgent: external_exports.string().optional(),
|
|
@@ -19059,15 +19096,38 @@ var AuditSummarySchema = external_exports.object({
|
|
|
19059
19096
|
var AuditQuerySchema = external_exports.object({
|
|
19060
19097
|
days: external_exports.coerce.number().int().refine((value) => AUDIT_RANGES.includes(value)).default(AUDIT_DEFAULT_DAYS),
|
|
19061
19098
|
page: external_exports.coerce.number().int().min(1).default(1),
|
|
19062
|
-
action:
|
|
19099
|
+
action: manyOf(external_exports.enum(AuditAction)),
|
|
19063
19100
|
actor: external_exports.enum(AuditActor).optional(),
|
|
19064
|
-
|
|
19101
|
+
account: manyOf(external_exports.string()),
|
|
19102
|
+
app: manyOf(external_exports.string())
|
|
19103
|
+
});
|
|
19104
|
+
var AuditFacetsSchema = external_exports.object({
|
|
19105
|
+
actions: external_exports.record(external_exports.string(), external_exports.number()),
|
|
19106
|
+
accounts: external_exports.array(external_exports.object({
|
|
19107
|
+
accountId: external_exports.string(),
|
|
19108
|
+
name: external_exports.string(),
|
|
19109
|
+
email: external_exports.string().optional(),
|
|
19110
|
+
image: external_exports.string().optional(),
|
|
19111
|
+
count: external_exports.number()
|
|
19112
|
+
})),
|
|
19113
|
+
apps: external_exports.array(external_exports.object({ kind: external_exports.string(), name: external_exports.string().optional(), count: external_exports.number() }))
|
|
19065
19114
|
});
|
|
19066
19115
|
var AuditResponseSchema = external_exports.object({
|
|
19067
19116
|
summary: AuditSummarySchema,
|
|
19068
19117
|
entries: external_exports.array(AuditEntrySchema),
|
|
19118
|
+
facets: AuditFacetsSchema,
|
|
19069
19119
|
total: external_exports.number()
|
|
19070
19120
|
});
|
|
19121
|
+
var VERB_OVERRIDES = {
|
|
19122
|
+
[AuditAction.McpToolCall]: "called",
|
|
19123
|
+
[AuditAction.McpRequest]: "called",
|
|
19124
|
+
[AuditAction.PageRead]: "read",
|
|
19125
|
+
[AuditAction.PageWrite]: "written",
|
|
19126
|
+
[AuditAction.AdminEnteredOrg]: "opened as support"
|
|
19127
|
+
};
|
|
19128
|
+
function manyOf(schema) {
|
|
19129
|
+
return external_exports.union([schema, external_exports.array(schema)]).optional().transform((value) => value === void 0 ? void 0 : Array.isArray(value) ? value : [value]);
|
|
19130
|
+
}
|
|
19071
19131
|
|
|
19072
19132
|
// ../../shared/dist/organization/install-admin.js
|
|
19073
19133
|
var InstallCredentialNameSchema = external_exports.enum([
|
|
@@ -19838,7 +19898,7 @@ var SaveWorkstationBodySchema = external_exports.object({
|
|
|
19838
19898
|
var WorkstationResponseSchema = external_exports.object({ workstation: WorkstationSchema });
|
|
19839
19899
|
|
|
19840
19900
|
// ../../shared/dist/machines/machine.js
|
|
19841
|
-
var CLI_VERSION = "0.
|
|
19901
|
+
var CLI_VERSION = "0.6.0";
|
|
19842
19902
|
var CLI_PACKAGE = "@craftspace/cli";
|
|
19843
19903
|
var CLI_INSTALL = `npm i -g ${CLI_PACKAGE}`;
|
|
19844
19904
|
var MACHINE_BEAT_INTERVAL_MS = 15e3;
|
|
@@ -19866,6 +19926,13 @@ var MachineToolSchema = external_exports.object({
|
|
|
19866
19926
|
ok: external_exports.boolean(),
|
|
19867
19927
|
detail: external_exports.string().max(200)
|
|
19868
19928
|
});
|
|
19929
|
+
var MachineRepoSchema = external_exports.object({
|
|
19930
|
+
name: external_exports.string().max(200),
|
|
19931
|
+
ref: external_exports.string().max(200),
|
|
19932
|
+
path: external_exports.string().max(400),
|
|
19933
|
+
ok: external_exports.boolean(),
|
|
19934
|
+
detail: external_exports.string().max(300)
|
|
19935
|
+
});
|
|
19869
19936
|
var MachineSchema = external_exports.object({
|
|
19870
19937
|
id: external_exports.string(),
|
|
19871
19938
|
tokenId: external_exports.string(),
|
|
@@ -19889,6 +19956,8 @@ var MachineSchema = external_exports.object({
|
|
|
19889
19956
|
tools: external_exports.array(MachineToolSchema),
|
|
19890
19957
|
environment: external_exports.array(WorkstationStepResultSchema),
|
|
19891
19958
|
templateId: external_exports.string().nullable(),
|
|
19959
|
+
workDir: external_exports.string().nullable(),
|
|
19960
|
+
repos: external_exports.array(MachineRepoSchema),
|
|
19892
19961
|
authorizedKeys: external_exports.array(MachineAuthorizedKeySchema),
|
|
19893
19962
|
lastBeatAt: external_exports.coerce.date().nullable(),
|
|
19894
19963
|
lastAskedAt: external_exports.coerce.date().nullable(),
|
|
@@ -19953,11 +20022,14 @@ var MachineBeatBodySchema = external_exports.object({
|
|
|
19953
20022
|
tools: external_exports.array(MachineToolSchema).max(10).default([]),
|
|
19954
20023
|
environment: external_exports.array(WorkstationStepResultSchema).max(WORKSTATION_MAX_STEPS).default([]),
|
|
19955
20024
|
templateId: external_exports.string().max(64).nullable().default(null),
|
|
20025
|
+
workDir: external_exports.string().max(400).optional(),
|
|
20026
|
+
repos: external_exports.array(MachineRepoSchema).max(WORKSTATION_MAX_REPOS).default([]),
|
|
19956
20027
|
sshUser: external_exports.string().min(1).max(64).optional()
|
|
19957
20028
|
});
|
|
19958
20029
|
var MachineEnvironmentSchema = external_exports.object({
|
|
19959
20030
|
templateId: external_exports.string().nullable(),
|
|
19960
|
-
steps: external_exports.array(WorkstationStepSchema)
|
|
20031
|
+
steps: external_exports.array(WorkstationStepSchema),
|
|
20032
|
+
repos: external_exports.array(WorkstationRepoSchema).default([])
|
|
19961
20033
|
});
|
|
19962
20034
|
var MachineBeatResponseSchema = external_exports.object({
|
|
19963
20035
|
cli: external_exports.object({ version: external_exports.string(), url: external_exports.string(), sha256: external_exports.string() }).nullable(),
|
|
@@ -20316,7 +20388,7 @@ var MemberActivityListResponse = external_exports.object({
|
|
|
20316
20388
|
|
|
20317
20389
|
// dist/machine.js
|
|
20318
20390
|
import { execFile as execFile3, spawn as spawn2 } from "node:child_process";
|
|
20319
|
-
import { mkdir, readFile as readFile2, readdir, rm, writeFile } from "node:fs/promises";
|
|
20391
|
+
import { mkdir as mkdir2, readFile as readFile2, readdir, rm, writeFile } from "node:fs/promises";
|
|
20320
20392
|
import os5 from "node:os";
|
|
20321
20393
|
import path4 from "node:path";
|
|
20322
20394
|
import { promisify as promisify2 } from "node:util";
|
|
@@ -21289,11 +21361,11 @@ function readlineWidth() {
|
|
|
21289
21361
|
|
|
21290
21362
|
// ../../../node_modules/@inquirer/core/dist/lib/pagination/use-pagination.js
|
|
21291
21363
|
function usePointerPosition({ active, renderedItems, pageSize, loop }) {
|
|
21292
|
-
const
|
|
21364
|
+
const state2 = useRef({
|
|
21293
21365
|
lastPointer: active,
|
|
21294
21366
|
lastActive: void 0
|
|
21295
21367
|
});
|
|
21296
|
-
const { lastPointer, lastActive } =
|
|
21368
|
+
const { lastPointer, lastActive } = state2.current;
|
|
21297
21369
|
const middle = Math.floor(pageSize / 2);
|
|
21298
21370
|
const renderedLength = renderedItems.reduce((acc, item) => acc + item.length, 0);
|
|
21299
21371
|
const defaultPointerPosition = renderedItems.slice(0, active).reduce((acc, item) => acc + item.length, 0);
|
|
@@ -21333,8 +21405,8 @@ function usePointerPosition({ active, renderedItems, pageSize, loop }) {
|
|
|
21333
21405
|
);
|
|
21334
21406
|
}
|
|
21335
21407
|
}
|
|
21336
|
-
|
|
21337
|
-
|
|
21408
|
+
state2.current.lastPointer = pointer;
|
|
21409
|
+
state2.current.lastActive = active;
|
|
21338
21410
|
return pointer;
|
|
21339
21411
|
}
|
|
21340
21412
|
function usePagination({ items, active, renderItem, pageSize, loop = true }) {
|
|
@@ -22237,7 +22309,7 @@ var runner = {
|
|
|
22237
22309
|
child.on("close", (code) => resolve(code ?? 0));
|
|
22238
22310
|
});
|
|
22239
22311
|
},
|
|
22240
|
-
async unattended({ work, onProgress }) {
|
|
22312
|
+
async unattended({ work, cwd = os2.homedir(), onProgress }) {
|
|
22241
22313
|
const argv = headlessArgv(work);
|
|
22242
22314
|
const raw = argv[0] === "script";
|
|
22243
22315
|
let output = "";
|
|
@@ -22267,7 +22339,7 @@ var runner = {
|
|
|
22267
22339
|
const code = await new Promise((resolve) => {
|
|
22268
22340
|
const child = spawn(argv[0], argv.slice(1), {
|
|
22269
22341
|
stdio: ["ignore", "pipe", "pipe"],
|
|
22270
|
-
cwd
|
|
22342
|
+
cwd,
|
|
22271
22343
|
env: {
|
|
22272
22344
|
...process.env,
|
|
22273
22345
|
TERM: process.env.TERM ?? "xterm-256color",
|
|
@@ -22365,6 +22437,7 @@ function parseJson(line) {
|
|
|
22365
22437
|
|
|
22366
22438
|
// dist/setup.js
|
|
22367
22439
|
import { execFile } from "node:child_process";
|
|
22440
|
+
import { mkdir } from "node:fs/promises";
|
|
22368
22441
|
import os4 from "node:os";
|
|
22369
22442
|
import path3 from "node:path";
|
|
22370
22443
|
|
|
@@ -22938,6 +23011,24 @@ var setup = {
|
|
|
22938
23011
|
if (missing.length > 0)
|
|
22939
23012
|
process.env.PATH = [...current, ...missing].join(path3.delimiter);
|
|
22940
23013
|
},
|
|
23014
|
+
workDir() {
|
|
23015
|
+
return path3.join(os4.homedir(), WORK_DIR_NAME);
|
|
23016
|
+
},
|
|
23017
|
+
async cloneRepos({ repos, say }) {
|
|
23018
|
+
if (repos.length === 0)
|
|
23019
|
+
return [];
|
|
23020
|
+
await mkdir(setup.workDir(), { recursive: true });
|
|
23021
|
+
const cloned = [];
|
|
23022
|
+
for (const [index, repo] of repos.entries()) {
|
|
23023
|
+
say?.(ui.progress({ name: repo.name, done: index, total: repos.length }));
|
|
23024
|
+
const at = path3.join(setup.workDir(), repo.name.split("/").pop() ?? repo.name);
|
|
23025
|
+
const outcome = await checkout(repo, at);
|
|
23026
|
+
cloned.push({ name: repo.name, ref: repo.ref, path: at, ...outcome });
|
|
23027
|
+
say?.(`${ui.clear()}${outcome.ok ? ui.ok(`${repo.name} \xB7 ${outcome.detail}`) : ui.bad(repo.name, outcome.detail)}`);
|
|
23028
|
+
}
|
|
23029
|
+
say?.(ui.tally({ done: cloned.filter((repo) => repo.ok).length, total: cloned.length, noun: "cloned" }));
|
|
23030
|
+
return cloned;
|
|
23031
|
+
},
|
|
22941
23032
|
async runSteps({ steps, say }) {
|
|
22942
23033
|
const results = [];
|
|
22943
23034
|
for (const [index, step] of steps.entries()) {
|
|
@@ -22953,6 +23044,43 @@ var setup = {
|
|
|
22953
23044
|
return results;
|
|
22954
23045
|
}
|
|
22955
23046
|
};
|
|
23047
|
+
async function checkout(repo, at) {
|
|
23048
|
+
const opened = await git(["-C", at, "rev-parse", "--abbrev-ref", "HEAD"]);
|
|
23049
|
+
if (!opened.ok) {
|
|
23050
|
+
const url2 = `https://github.com/${repo.name}.git`;
|
|
23051
|
+
const made = await git(["clone", "--depth", String(CLONE_DEPTH), "--branch", repo.ref, url2, at]);
|
|
23052
|
+
if (!made.ok)
|
|
23053
|
+
return { ok: false, detail: made.detail };
|
|
23054
|
+
return { ok: true, detail: await state({ repo, at, branch: repo.ref, dirty: false }) };
|
|
23055
|
+
}
|
|
23056
|
+
const branch = opened.detail;
|
|
23057
|
+
const dirty = (await git(["-C", at, "status", "--porcelain"])).detail !== "";
|
|
23058
|
+
const pulled = branch === repo.ref && !dirty ? await git(["-C", at, "pull", "--ff-only"]) : null;
|
|
23059
|
+
return { ok: true, detail: await state({ repo, at, branch, dirty, pulled }) };
|
|
23060
|
+
}
|
|
23061
|
+
async function state({ repo, at, branch, dirty, pulled }) {
|
|
23062
|
+
const head = await git(["-C", at, "rev-parse", "--short", "HEAD"]);
|
|
23063
|
+
return [
|
|
23064
|
+
`${branch} @ ${head.detail}`,
|
|
23065
|
+
branch === repo.ref ? "" : `your team asks for ${repo.ref}`,
|
|
23066
|
+
dirty ? "uncommitted work here, so it was left alone" : "",
|
|
23067
|
+
pulled?.ok === false ? `could not pull: ${pulled.detail}` : ""
|
|
23068
|
+
].filter((part) => part !== "").join(" \xB7 ");
|
|
23069
|
+
}
|
|
23070
|
+
function git(argv) {
|
|
23071
|
+
return new Promise((resolve) => {
|
|
23072
|
+
execFile("git", argv, {
|
|
23073
|
+
timeout: CLONE_TIMEOUT_MS,
|
|
23074
|
+
maxBuffer: SCRIPT_BUFFER_BYTES,
|
|
23075
|
+
env: { ...process.env, GIT_TERMINAL_PROMPT: "0", GIT_ASKPASS: "", GIT_LFS_SKIP_SMUDGE: "1" }
|
|
23076
|
+
}, (error51, stdout, stderr) => {
|
|
23077
|
+
if (error51 === null)
|
|
23078
|
+
return resolve({ ok: true, detail: stdout.trim() });
|
|
23079
|
+
resolve({ ok: false, detail: lastLines(`${stderr}
|
|
23080
|
+
${stdout}`) || error51.message.split("\n")[0] || "git failed" });
|
|
23081
|
+
});
|
|
23082
|
+
});
|
|
23083
|
+
}
|
|
22956
23084
|
function runScript(script) {
|
|
22957
23085
|
return new Promise((resolve) => {
|
|
22958
23086
|
execFile("/bin/sh", ["-e", "-c", script], { timeout: STEP_TIMEOUT_MS, maxBuffer: SCRIPT_BUFFER_BYTES }, (error51, stdout, stderr) => {
|
|
@@ -22977,6 +23105,9 @@ function toolDirs() {
|
|
|
22977
23105
|
"/usr/local/bin"
|
|
22978
23106
|
];
|
|
22979
23107
|
}
|
|
23108
|
+
var WORK_DIR_NAME = "craftspace";
|
|
23109
|
+
var CLONE_DEPTH = 20;
|
|
23110
|
+
var CLONE_TIMEOUT_MS = 9e5;
|
|
22980
23111
|
var STEP_TIMEOUT_MS = 6e5;
|
|
22981
23112
|
var SCRIPT_BUFFER_BYTES = 4 * 1024 * 1024;
|
|
22982
23113
|
var KEPT_LINES = 3;
|
|
@@ -23051,7 +23182,7 @@ var machine = {
|
|
|
23051
23182
|
},
|
|
23052
23183
|
schema: MachineLoginResponseSchema
|
|
23053
23184
|
});
|
|
23054
|
-
await
|
|
23185
|
+
await mkdir2(machine.home(), { recursive: true, mode: 448 });
|
|
23055
23186
|
await writeFile(configPath(), JSON.stringify({ url: url2, machineId: answer.machine.id }, null, 2), { mode: 384 });
|
|
23056
23187
|
await writeFile(tokenPath(), token, { mode: 384 });
|
|
23057
23188
|
await writeAgentConfigs({ url: url2, token, writes: answer.write });
|
|
@@ -23110,6 +23241,8 @@ ${ui.ok(`Signed in as ${signed.ownerName}. This machine is ${ui.name(signed.name
|
|
|
23110
23241
|
tools: await checks(),
|
|
23111
23242
|
environment: built?.results ?? [],
|
|
23112
23243
|
templateId: built?.templateId ?? null,
|
|
23244
|
+
workDir: setup.workDir(),
|
|
23245
|
+
repos: built?.repos ?? [],
|
|
23113
23246
|
sshUser: os5.userInfo().username
|
|
23114
23247
|
},
|
|
23115
23248
|
schema: MachineBeatResponseSchema
|
|
@@ -23124,7 +23257,7 @@ ${ui.ok(`Signed in as ${signed.ownerName}. This machine is ${ui.name(signed.name
|
|
|
23124
23257
|
if (running.has(work.id))
|
|
23125
23258
|
continue;
|
|
23126
23259
|
running.add(work.id);
|
|
23127
|
-
void runner.unattended({ work, onProgress: report }).then(report).catch((error51) => report({ id: work.id, state: "failed", output: messageOf(error51) })).finally(() => running.delete(work.id));
|
|
23260
|
+
void runner.unattended({ work, cwd: startIn(built), onProgress: report }).then(report).catch((error51) => report({ id: work.id, state: "failed", output: messageOf(error51) })).finally(() => running.delete(work.id));
|
|
23128
23261
|
}
|
|
23129
23262
|
return running.size > 0 || answer.work.length > 0;
|
|
23130
23263
|
},
|
|
@@ -23133,7 +23266,7 @@ ${ui.ok(`Signed in as ${signed.ownerName}. This machine is ${ui.name(signed.name
|
|
|
23133
23266
|
let busy = false;
|
|
23134
23267
|
let checkedAt = 0;
|
|
23135
23268
|
for (; ; ) {
|
|
23136
|
-
if (!busy && Date.now() - checkedAt > UPDATE_EVERY_MS) {
|
|
23269
|
+
if (!busy && (failures > 2 || Date.now() - checkedAt > UPDATE_EVERY_MS)) {
|
|
23137
23270
|
checkedAt = Date.now();
|
|
23138
23271
|
const moved = await update.toLatest();
|
|
23139
23272
|
if (moved !== null) {
|
|
@@ -23260,17 +23393,40 @@ async function buildEnvironment(next, say) {
|
|
|
23260
23393
|
if (building || !installs() || next.templateId === null)
|
|
23261
23394
|
return;
|
|
23262
23395
|
const built = await readEnvironment();
|
|
23263
|
-
|
|
23396
|
+
const buildsSteps = buildIsDue(built, next.templateId);
|
|
23397
|
+
const clonesRepos = cloneIsDue(built, next.repos);
|
|
23398
|
+
if (!buildsSteps && !clonesRepos)
|
|
23264
23399
|
return;
|
|
23265
23400
|
building = true;
|
|
23266
23401
|
try {
|
|
23267
|
-
|
|
23268
|
-
|
|
23269
|
-
|
|
23402
|
+
let results = built?.results ?? [];
|
|
23403
|
+
let repos = built?.repos ?? [];
|
|
23404
|
+
if (buildsSteps) {
|
|
23405
|
+
say?.(ui.heading(`Building the ${next.steps.length} setup scripts your team runs on every box`));
|
|
23406
|
+
results = await setup.runSteps({ steps: next.steps, say });
|
|
23407
|
+
}
|
|
23408
|
+
if (clonesRepos) {
|
|
23409
|
+
say?.(ui.heading(`Cloning the ${next.repos.length} repos your team works in, into ${setup.workDir()}`));
|
|
23410
|
+
repos = await setup.cloneRepos({ repos: next.repos, say });
|
|
23411
|
+
await acceptClaudeGates(["craftspace"], repos.filter((repo) => repo.ok).map((repo) => repo.path));
|
|
23412
|
+
}
|
|
23413
|
+
await writeEnvironment({ templateId: next.templateId, results, repos, at: Date.now() });
|
|
23270
23414
|
} finally {
|
|
23271
23415
|
building = false;
|
|
23272
23416
|
}
|
|
23273
23417
|
}
|
|
23418
|
+
function cloneIsDue(built, wanted) {
|
|
23419
|
+
const cloned = built?.repos ?? [];
|
|
23420
|
+
if (wanted.length === 0 && cloned.length === 0)
|
|
23421
|
+
return false;
|
|
23422
|
+
if (wanted.length !== cloned.length)
|
|
23423
|
+
return true;
|
|
23424
|
+
const missing = wanted.some((repo) => !cloned.some((seen) => seen.name === repo.name && seen.ref === repo.ref && seen.ok));
|
|
23425
|
+
return missing || Date.now() - (built?.at ?? 0) > REBUILD_AFTER_MS;
|
|
23426
|
+
}
|
|
23427
|
+
function startIn(built) {
|
|
23428
|
+
return built?.repos.find((repo) => repo.ok)?.path ?? os5.homedir();
|
|
23429
|
+
}
|
|
23274
23430
|
function buildIsDue(built, templateId) {
|
|
23275
23431
|
if (built === null || built.templateId !== templateId)
|
|
23276
23432
|
return true;
|
|
@@ -23286,7 +23442,7 @@ async function readEnvironment() {
|
|
|
23286
23442
|
return parsed.success ? parsed.data : null;
|
|
23287
23443
|
}
|
|
23288
23444
|
async function writeEnvironment(built) {
|
|
23289
|
-
await
|
|
23445
|
+
await mkdir2(machine.home(), { recursive: true, mode: 448 });
|
|
23290
23446
|
await writeFile(environmentPath(), JSON.stringify(built, null, 2), { mode: 384 });
|
|
23291
23447
|
}
|
|
23292
23448
|
function installs() {
|
|
@@ -23363,7 +23519,7 @@ async function ensureKey() {
|
|
|
23363
23519
|
const existing = await readFile2(pub, "utf8").catch(() => null);
|
|
23364
23520
|
if (existing !== null)
|
|
23365
23521
|
return existing.trim();
|
|
23366
|
-
await
|
|
23522
|
+
await mkdir2(machine.home(), { recursive: true, mode: 448 });
|
|
23367
23523
|
await run2("ssh-keygen", ["-t", "ed25519", "-N", "", "-q", "-f", priv, "-C", `craftspace-${os5.hostname()}`]);
|
|
23368
23524
|
return (await readFile2(pub, "utf8")).trim();
|
|
23369
23525
|
}
|
|
@@ -23403,7 +23559,7 @@ ${KEYS_END}
|
|
|
23403
23559
|
const next = `${trimEnd(before)}${trimEnd(before) === "" ? "" : "\n"}${block}${after.replace(/^\n/, "")}`;
|
|
23404
23560
|
if (next === current)
|
|
23405
23561
|
return;
|
|
23406
|
-
await
|
|
23562
|
+
await mkdir2(path4.dirname(target), { recursive: true, mode: 448 });
|
|
23407
23563
|
await writeFile(target, next, { mode: 384 });
|
|
23408
23564
|
}
|
|
23409
23565
|
function trimEnd(text) {
|
|
@@ -23414,7 +23570,7 @@ async function writeAgentConfigs({ url: url2, token, writes }) {
|
|
|
23414
23570
|
const owned = [];
|
|
23415
23571
|
for (const write of planned) {
|
|
23416
23572
|
const target = expand(write.path);
|
|
23417
|
-
await
|
|
23573
|
+
await mkdir2(path4.dirname(target), { recursive: true });
|
|
23418
23574
|
const current = await readFile2(target, "utf8").catch(() => null);
|
|
23419
23575
|
if (current !== null)
|
|
23420
23576
|
await writeFile(`${target}.craftspace-backup`, current, { flag: "wx" }).catch(() => void 0);
|
|
@@ -23429,22 +23585,29 @@ async function writeAgentConfigs({ url: url2, token, writes }) {
|
|
|
23429
23585
|
function serversIn(writes) {
|
|
23430
23586
|
return writes.flatMap((write) => isPlainObject3(write.contents.mcpServers) ? Object.keys(write.contents.mcpServers) : []);
|
|
23431
23587
|
}
|
|
23432
|
-
async function acceptClaudeGates(servers) {
|
|
23588
|
+
async function acceptClaudeGates(servers, dirs = [os5.homedir()]) {
|
|
23433
23589
|
const target = path4.join(os5.homedir(), ".claude.json");
|
|
23434
23590
|
const raw = await readFile2(target, "utf8").catch(() => null);
|
|
23435
23591
|
if (raw === null)
|
|
23436
23592
|
return;
|
|
23437
23593
|
const parsed = JSON.parse(raw);
|
|
23438
23594
|
const projects = isPlainObject3(parsed.projects) ? parsed.projects : {};
|
|
23439
|
-
const
|
|
23440
|
-
|
|
23441
|
-
const
|
|
23442
|
-
|
|
23595
|
+
const opened = { ...projects };
|
|
23596
|
+
let moved = false;
|
|
23597
|
+
for (const dir of dirs) {
|
|
23598
|
+
const found = opened[dir];
|
|
23599
|
+
const here2 = isPlainObject3(found) ? { ...found } : {};
|
|
23600
|
+
const enabled = /* @__PURE__ */ new Set([...asArray(here2.enabledMcpjsonServers), ...servers]);
|
|
23601
|
+
if (here2.hasTrustDialogAccepted === true && enabled.size === asArray(here2.enabledMcpjsonServers).length)
|
|
23602
|
+
continue;
|
|
23603
|
+
here2.hasTrustDialogAccepted = true;
|
|
23604
|
+
here2.enabledMcpjsonServers = [...enabled];
|
|
23605
|
+
opened[dir] = here2;
|
|
23606
|
+
moved = true;
|
|
23607
|
+
}
|
|
23608
|
+
if (!moved)
|
|
23443
23609
|
return;
|
|
23444
|
-
|
|
23445
|
-
here2.enabledMcpjsonServers = [...enabled];
|
|
23446
|
-
const next = { ...parsed, projects: { ...projects, [os5.homedir()]: here2 } };
|
|
23447
|
-
await writeFile(target, `${JSON.stringify(next, null, 2)}
|
|
23610
|
+
await writeFile(target, `${JSON.stringify({ ...parsed, projects: opened }, null, 2)}
|
|
23448
23611
|
`, { mode: 384 });
|
|
23449
23612
|
}
|
|
23450
23613
|
function asArray(value) {
|
|
@@ -23495,16 +23658,17 @@ async function installService() {
|
|
|
23495
23658
|
return;
|
|
23496
23659
|
if (process.platform === "linux") {
|
|
23497
23660
|
const dir = path4.join(os5.homedir(), ".config", "systemd", "user");
|
|
23498
|
-
await
|
|
23661
|
+
await mkdir2(dir, { recursive: true });
|
|
23499
23662
|
await writeFile(path4.join(dir, `${SERVICE_NAME}.service`), systemdUnit());
|
|
23500
23663
|
await run2("loginctl", ["enable-linger", os5.userInfo().username]).catch(() => void 0);
|
|
23501
23664
|
await run2("systemctl", ["--user", "daemon-reload"]).catch(() => void 0);
|
|
23502
|
-
await run2("systemctl", ["--user", "enable",
|
|
23665
|
+
await run2("systemctl", ["--user", "enable", SERVICE_NAME]).catch(() => void 0);
|
|
23666
|
+
await run2("systemctl", ["--user", "restart", SERVICE_NAME]).catch(() => void 0);
|
|
23503
23667
|
return;
|
|
23504
23668
|
}
|
|
23505
23669
|
if (process.platform === "darwin") {
|
|
23506
23670
|
const target = path4.join(os5.homedir(), "Library", "LaunchAgents", `${LAUNCH_LABEL}.plist`);
|
|
23507
|
-
await
|
|
23671
|
+
await mkdir2(path4.dirname(target), { recursive: true });
|
|
23508
23672
|
await writeFile(target, launchdPlist());
|
|
23509
23673
|
await run2("launchctl", ["unload", target]).catch(() => void 0);
|
|
23510
23674
|
await run2("launchctl", ["load", target]).catch(() => void 0);
|
|
@@ -23600,6 +23764,7 @@ var ConfigSchema = external_exports.object({ url: external_exports.string(), mac
|
|
|
23600
23764
|
var BuiltSchema = external_exports.object({
|
|
23601
23765
|
templateId: external_exports.string(),
|
|
23602
23766
|
results: external_exports.array(WorkstationStepResultSchema),
|
|
23767
|
+
repos: external_exports.array(MachineRepoSchema).default([]),
|
|
23603
23768
|
at: external_exports.number()
|
|
23604
23769
|
});
|
|
23605
23770
|
var OwnedSchema = external_exports.object({ paths: external_exports.array(external_exports.string()) });
|
package/dist/machine.js
CHANGED
|
@@ -3,7 +3,7 @@ import { mkdir, readFile, readdir, rm, writeFile } from 'node:fs/promises';
|
|
|
3
3
|
import os from 'node:os';
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import { promisify } from 'node:util';
|
|
6
|
-
import { CLI_VERSION, MACHINE_BEAT_BUSY_INTERVAL_MS, MACHINE_BEAT_INTERVAL_MS, MachineBeatResponseSchema, MachineLoginResponseSchema, MachineSchema, MachineSessionSchema, MachinesResponseSchema, WorkstationStepResultSchema, } from '@craftspace/shared';
|
|
6
|
+
import { CLI_VERSION, MACHINE_BEAT_BUSY_INTERVAL_MS, MACHINE_BEAT_INTERVAL_MS, MachineBeatResponseSchema, MachineLoginResponseSchema, MachineSchema, MachineRepoSchema, MachineSessionSchema, MachinesResponseSchema, WorkstationStepResultSchema, } from '@craftspace/shared';
|
|
7
7
|
import { confirm, input, select } from '@inquirer/prompts';
|
|
8
8
|
import { z } from 'zod';
|
|
9
9
|
import { probe } from './probe.js';
|
|
@@ -89,6 +89,8 @@ export const machine = {
|
|
|
89
89
|
tools: await checks(),
|
|
90
90
|
environment: built?.results ?? [],
|
|
91
91
|
templateId: built?.templateId ?? null,
|
|
92
|
+
workDir: setup.workDir(),
|
|
93
|
+
repos: built?.repos ?? [],
|
|
92
94
|
sshUser: os.userInfo().username,
|
|
93
95
|
},
|
|
94
96
|
schema: MachineBeatResponseSchema,
|
|
@@ -104,7 +106,7 @@ export const machine = {
|
|
|
104
106
|
continue;
|
|
105
107
|
running.add(work.id);
|
|
106
108
|
void runner
|
|
107
|
-
.unattended({ work, onProgress: report })
|
|
109
|
+
.unattended({ work, cwd: startIn(built), onProgress: report })
|
|
108
110
|
.then(report)
|
|
109
111
|
.catch((error) => report({ id: work.id, state: 'failed', output: messageOf(error) }))
|
|
110
112
|
.finally(() => running.delete(work.id));
|
|
@@ -116,7 +118,7 @@ export const machine = {
|
|
|
116
118
|
let busy = false;
|
|
117
119
|
let checkedAt = 0;
|
|
118
120
|
for (;;) {
|
|
119
|
-
if (!busy && Date.now() - checkedAt > UPDATE_EVERY_MS) {
|
|
121
|
+
if (!busy && (failures > 2 || Date.now() - checkedAt > UPDATE_EVERY_MS)) {
|
|
120
122
|
checkedAt = Date.now();
|
|
121
123
|
const moved = await update.toLatest();
|
|
122
124
|
if (moved !== null) {
|
|
@@ -238,18 +240,41 @@ async function buildEnvironment(next, say) {
|
|
|
238
240
|
if (building || !installs() || next.templateId === null)
|
|
239
241
|
return;
|
|
240
242
|
const built = await readEnvironment();
|
|
241
|
-
|
|
243
|
+
const buildsSteps = buildIsDue(built, next.templateId);
|
|
244
|
+
const clonesRepos = cloneIsDue(built, next.repos);
|
|
245
|
+
if (!buildsSteps && !clonesRepos)
|
|
242
246
|
return;
|
|
243
247
|
building = true;
|
|
244
248
|
try {
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
249
|
+
let results = built?.results ?? [];
|
|
250
|
+
let repos = built?.repos ?? [];
|
|
251
|
+
if (buildsSteps) {
|
|
252
|
+
say?.(ui.heading(`Building the ${next.steps.length} setup scripts your team runs on every box`));
|
|
253
|
+
results = await setup.runSteps({ steps: next.steps, say });
|
|
254
|
+
}
|
|
255
|
+
if (clonesRepos) {
|
|
256
|
+
say?.(ui.heading(`Cloning the ${next.repos.length} repos your team works in, into ${setup.workDir()}`));
|
|
257
|
+
repos = await setup.cloneRepos({ repos: next.repos, say });
|
|
258
|
+
await acceptClaudeGates(['craftspace'], repos.filter((repo) => repo.ok).map((repo) => repo.path));
|
|
259
|
+
}
|
|
260
|
+
await writeEnvironment({ templateId: next.templateId, results, repos, at: Date.now() });
|
|
248
261
|
}
|
|
249
262
|
finally {
|
|
250
263
|
building = false;
|
|
251
264
|
}
|
|
252
265
|
}
|
|
266
|
+
function cloneIsDue(built, wanted) {
|
|
267
|
+
const cloned = built?.repos ?? [];
|
|
268
|
+
if (wanted.length === 0 && cloned.length === 0)
|
|
269
|
+
return false;
|
|
270
|
+
if (wanted.length !== cloned.length)
|
|
271
|
+
return true;
|
|
272
|
+
const missing = wanted.some((repo) => !cloned.some((seen) => seen.name === repo.name && seen.ref === repo.ref && seen.ok));
|
|
273
|
+
return missing || Date.now() - (built?.at ?? 0) > REBUILD_AFTER_MS;
|
|
274
|
+
}
|
|
275
|
+
function startIn(built) {
|
|
276
|
+
return built?.repos.find((repo) => repo.ok)?.path ?? os.homedir();
|
|
277
|
+
}
|
|
253
278
|
function buildIsDue(built, templateId) {
|
|
254
279
|
if (built === null || built.templateId !== templateId)
|
|
255
280
|
return true;
|
|
@@ -403,22 +428,29 @@ async function writeAgentConfigs({ url, token, writes, }) {
|
|
|
403
428
|
function serversIn(writes) {
|
|
404
429
|
return writes.flatMap((write) => isPlainObject(write.contents.mcpServers) ? Object.keys(write.contents.mcpServers) : []);
|
|
405
430
|
}
|
|
406
|
-
async function acceptClaudeGates(servers) {
|
|
431
|
+
async function acceptClaudeGates(servers, dirs = [os.homedir()]) {
|
|
407
432
|
const target = path.join(os.homedir(), '.claude.json');
|
|
408
433
|
const raw = await readFile(target, 'utf8').catch(() => null);
|
|
409
434
|
if (raw === null)
|
|
410
435
|
return;
|
|
411
436
|
const parsed = JSON.parse(raw);
|
|
412
437
|
const projects = isPlainObject(parsed.projects) ? parsed.projects : {};
|
|
413
|
-
const
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
438
|
+
const opened = { ...projects };
|
|
439
|
+
let moved = false;
|
|
440
|
+
for (const dir of dirs) {
|
|
441
|
+
const found = opened[dir];
|
|
442
|
+
const here = isPlainObject(found) ? { ...found } : {};
|
|
443
|
+
const enabled = new Set([...asArray(here.enabledMcpjsonServers), ...servers]);
|
|
444
|
+
if (here.hasTrustDialogAccepted === true && enabled.size === asArray(here.enabledMcpjsonServers).length)
|
|
445
|
+
continue;
|
|
446
|
+
here.hasTrustDialogAccepted = true;
|
|
447
|
+
here.enabledMcpjsonServers = [...enabled];
|
|
448
|
+
opened[dir] = here;
|
|
449
|
+
moved = true;
|
|
450
|
+
}
|
|
451
|
+
if (!moved)
|
|
417
452
|
return;
|
|
418
|
-
|
|
419
|
-
here.enabledMcpjsonServers = [...enabled];
|
|
420
|
-
const next = { ...parsed, projects: { ...projects, [os.homedir()]: here } };
|
|
421
|
-
await writeFile(target, `${JSON.stringify(next, null, 2)}\n`, { mode: 0o600 });
|
|
453
|
+
await writeFile(target, `${JSON.stringify({ ...parsed, projects: opened }, null, 2)}\n`, { mode: 0o600 });
|
|
422
454
|
}
|
|
423
455
|
function asArray(value) {
|
|
424
456
|
return Array.isArray(value) ? value : [];
|
|
@@ -472,7 +504,8 @@ async function installService() {
|
|
|
472
504
|
await writeFile(path.join(dir, `${SERVICE_NAME}.service`), systemdUnit());
|
|
473
505
|
await run('loginctl', ['enable-linger', os.userInfo().username]).catch(() => undefined);
|
|
474
506
|
await run('systemctl', ['--user', 'daemon-reload']).catch(() => undefined);
|
|
475
|
-
await run('systemctl', ['--user', 'enable',
|
|
507
|
+
await run('systemctl', ['--user', 'enable', SERVICE_NAME]).catch(() => undefined);
|
|
508
|
+
await run('systemctl', ['--user', 'restart', SERVICE_NAME]).catch(() => undefined);
|
|
476
509
|
return;
|
|
477
510
|
}
|
|
478
511
|
if (process.platform === 'darwin') {
|
|
@@ -573,6 +606,7 @@ const ConfigSchema = z.object({ url: z.string(), machineId: z.string() });
|
|
|
573
606
|
const BuiltSchema = z.object({
|
|
574
607
|
templateId: z.string(),
|
|
575
608
|
results: z.array(WorkstationStepResultSchema),
|
|
609
|
+
repos: z.array(MachineRepoSchema).default([]),
|
|
576
610
|
at: z.number(),
|
|
577
611
|
});
|
|
578
612
|
const OwnedSchema = z.object({ paths: z.array(z.string()) });
|
package/dist/run.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { type MachineRunReport, type MachineWork } from '@craftspace/shared';
|
|
2
2
|
export declare const runner: {
|
|
3
3
|
attended(argv: string[]): Promise<number>;
|
|
4
|
-
unattended({ work, onProgress, }: {
|
|
4
|
+
unattended({ work, cwd, onProgress, }: {
|
|
5
5
|
work: MachineWork;
|
|
6
|
+
cwd?: string;
|
|
6
7
|
onProgress?: (report: MachineRunReport) => void;
|
|
7
8
|
}): Promise<MachineRunReport>;
|
|
8
9
|
};
|
package/dist/run.js
CHANGED
|
@@ -9,7 +9,7 @@ export const runner = {
|
|
|
9
9
|
child.on('close', (code) => resolve(code ?? 0));
|
|
10
10
|
});
|
|
11
11
|
},
|
|
12
|
-
async unattended({ work, onProgress, }) {
|
|
12
|
+
async unattended({ work, cwd = os.homedir(), onProgress, }) {
|
|
13
13
|
const argv = headlessArgv(work);
|
|
14
14
|
const raw = argv[0] === 'script';
|
|
15
15
|
let output = '';
|
|
@@ -37,7 +37,7 @@ export const runner = {
|
|
|
37
37
|
const code = await new Promise((resolve) => {
|
|
38
38
|
const child = spawn(argv[0], argv.slice(1), {
|
|
39
39
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
40
|
-
cwd
|
|
40
|
+
cwd,
|
|
41
41
|
env: {
|
|
42
42
|
...process.env,
|
|
43
43
|
TERM: process.env.TERM ?? 'xterm-256color',
|
package/dist/setup.d.ts
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
|
-
import { type WorkstationStep, type WorkstationStepResult } from '@craftspace/shared';
|
|
1
|
+
import { type MachineRepo, type WorkstationRepo, type WorkstationStep, type WorkstationStepResult } from '@craftspace/shared';
|
|
2
2
|
export declare const setup: {
|
|
3
3
|
widenPath(): void;
|
|
4
|
+
workDir(): string;
|
|
5
|
+
cloneRepos({ repos, say, }: {
|
|
6
|
+
repos: WorkstationRepo[];
|
|
7
|
+
say?: (line: string) => void;
|
|
8
|
+
}): Promise<MachineRepo[]>;
|
|
4
9
|
runSteps({ steps, say, }: {
|
|
5
10
|
steps: WorkstationStep[];
|
|
6
11
|
say?: (line: string) => void;
|
package/dist/setup.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { execFile } from 'node:child_process';
|
|
2
|
+
import { mkdir } from 'node:fs/promises';
|
|
2
3
|
import os from 'node:os';
|
|
3
4
|
import path from 'node:path';
|
|
4
5
|
import { WORKSTATION_STEP_DETAIL_MAX_CHARS, } from '@craftspace/shared';
|
|
@@ -10,6 +11,24 @@ export const setup = {
|
|
|
10
11
|
if (missing.length > 0)
|
|
11
12
|
process.env.PATH = [...current, ...missing].join(path.delimiter);
|
|
12
13
|
},
|
|
14
|
+
workDir() {
|
|
15
|
+
return path.join(os.homedir(), WORK_DIR_NAME);
|
|
16
|
+
},
|
|
17
|
+
async cloneRepos({ repos, say, }) {
|
|
18
|
+
if (repos.length === 0)
|
|
19
|
+
return [];
|
|
20
|
+
await mkdir(setup.workDir(), { recursive: true });
|
|
21
|
+
const cloned = [];
|
|
22
|
+
for (const [index, repo] of repos.entries()) {
|
|
23
|
+
say?.(ui.progress({ name: repo.name, done: index, total: repos.length }));
|
|
24
|
+
const at = path.join(setup.workDir(), repo.name.split('/').pop() ?? repo.name);
|
|
25
|
+
const outcome = await checkout(repo, at);
|
|
26
|
+
cloned.push({ name: repo.name, ref: repo.ref, path: at, ...outcome });
|
|
27
|
+
say?.(`${ui.clear()}${outcome.ok ? ui.ok(`${repo.name} · ${outcome.detail}`) : ui.bad(repo.name, outcome.detail)}`);
|
|
28
|
+
}
|
|
29
|
+
say?.(ui.tally({ done: cloned.filter((repo) => repo.ok).length, total: cloned.length, noun: 'cloned' }));
|
|
30
|
+
return cloned;
|
|
31
|
+
},
|
|
13
32
|
async runSteps({ steps, say, }) {
|
|
14
33
|
const results = [];
|
|
15
34
|
for (const [index, step] of steps.entries()) {
|
|
@@ -25,6 +44,44 @@ export const setup = {
|
|
|
25
44
|
return results;
|
|
26
45
|
},
|
|
27
46
|
};
|
|
47
|
+
async function checkout(repo, at) {
|
|
48
|
+
const opened = await git(['-C', at, 'rev-parse', '--abbrev-ref', 'HEAD']);
|
|
49
|
+
if (!opened.ok) {
|
|
50
|
+
const url = `https://github.com/${repo.name}.git`;
|
|
51
|
+
const made = await git(['clone', '--depth', String(CLONE_DEPTH), '--branch', repo.ref, url, at]);
|
|
52
|
+
if (!made.ok)
|
|
53
|
+
return { ok: false, detail: made.detail };
|
|
54
|
+
return { ok: true, detail: await state({ repo, at, branch: repo.ref, dirty: false }) };
|
|
55
|
+
}
|
|
56
|
+
const branch = opened.detail;
|
|
57
|
+
const dirty = (await git(['-C', at, 'status', '--porcelain'])).detail !== '';
|
|
58
|
+
const pulled = branch === repo.ref && !dirty ? await git(['-C', at, 'pull', '--ff-only']) : null;
|
|
59
|
+
return { ok: true, detail: await state({ repo, at, branch, dirty, pulled }) };
|
|
60
|
+
}
|
|
61
|
+
async function state({ repo, at, branch, dirty, pulled, }) {
|
|
62
|
+
const head = await git(['-C', at, 'rev-parse', '--short', 'HEAD']);
|
|
63
|
+
return [
|
|
64
|
+
`${branch} @ ${head.detail}`,
|
|
65
|
+
branch === repo.ref ? '' : `your team asks for ${repo.ref}`,
|
|
66
|
+
dirty ? 'uncommitted work here, so it was left alone' : '',
|
|
67
|
+
pulled?.ok === false ? `could not pull: ${pulled.detail}` : '',
|
|
68
|
+
]
|
|
69
|
+
.filter((part) => part !== '')
|
|
70
|
+
.join(' · ');
|
|
71
|
+
}
|
|
72
|
+
function git(argv) {
|
|
73
|
+
return new Promise((resolve) => {
|
|
74
|
+
execFile('git', argv, {
|
|
75
|
+
timeout: CLONE_TIMEOUT_MS,
|
|
76
|
+
maxBuffer: SCRIPT_BUFFER_BYTES,
|
|
77
|
+
env: { ...process.env, GIT_TERMINAL_PROMPT: '0', GIT_ASKPASS: '', GIT_LFS_SKIP_SMUDGE: '1' },
|
|
78
|
+
}, (error, stdout, stderr) => {
|
|
79
|
+
if (error === null)
|
|
80
|
+
return resolve({ ok: true, detail: stdout.trim() });
|
|
81
|
+
resolve({ ok: false, detail: lastLines(`${stderr}\n${stdout}`) || error.message.split('\n')[0] || 'git failed' });
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
}
|
|
28
85
|
function runScript(script) {
|
|
29
86
|
return new Promise((resolve) => {
|
|
30
87
|
execFile('/bin/sh', ['-e', '-c', script], { timeout: STEP_TIMEOUT_MS, maxBuffer: SCRIPT_BUFFER_BYTES }, (error, stdout, stderr) => {
|
|
@@ -54,6 +111,9 @@ function toolDirs() {
|
|
|
54
111
|
'/usr/local/bin',
|
|
55
112
|
];
|
|
56
113
|
}
|
|
114
|
+
const WORK_DIR_NAME = 'craftspace';
|
|
115
|
+
const CLONE_DEPTH = 20;
|
|
116
|
+
const CLONE_TIMEOUT_MS = 900_000;
|
|
57
117
|
const STEP_TIMEOUT_MS = 600_000;
|
|
58
118
|
const SCRIPT_BUFFER_BYTES = 4 * 1024 * 1024;
|
|
59
119
|
const KEPT_LINES = 3;
|