@craftspace/cli 0.5.0 → 0.7.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 +218 -47
- package/dist/machine.js +58 -18
- 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.7.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,10 +23241,14 @@ ${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
|
|
23116
23249
|
});
|
|
23250
|
+
if (answer.cli !== null && isNewer(answer.cli.version))
|
|
23251
|
+
updateWanted = answer.cli.version;
|
|
23117
23252
|
await writeAuthorizedKeys(answer.authorizedKeys);
|
|
23118
23253
|
const building2 = buildEnvironment(answer.environment, say);
|
|
23119
23254
|
if (say === void 0)
|
|
@@ -23124,7 +23259,7 @@ ${ui.ok(`Signed in as ${signed.ownerName}. This machine is ${ui.name(signed.name
|
|
|
23124
23259
|
if (running.has(work.id))
|
|
23125
23260
|
continue;
|
|
23126
23261
|
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));
|
|
23262
|
+
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
23263
|
}
|
|
23129
23264
|
return running.size > 0 || answer.work.length > 0;
|
|
23130
23265
|
},
|
|
@@ -23133,8 +23268,10 @@ ${ui.ok(`Signed in as ${signed.ownerName}. This machine is ${ui.name(signed.name
|
|
|
23133
23268
|
let busy = false;
|
|
23134
23269
|
let checkedAt = 0;
|
|
23135
23270
|
for (; ; ) {
|
|
23136
|
-
|
|
23271
|
+
const asked = updateWanted !== null && Date.now() - checkedAt > UPDATE_AFTER_BEAT_MS;
|
|
23272
|
+
if (!busy && (asked || failures > 2 || Date.now() - checkedAt > UPDATE_EVERY_MS)) {
|
|
23137
23273
|
checkedAt = Date.now();
|
|
23274
|
+
updateWanted = null;
|
|
23138
23275
|
const moved = await update.toLatest();
|
|
23139
23276
|
if (moved !== null) {
|
|
23140
23277
|
process.stderr.write(`craftspace ${CLI_VERSION} -> ${moved}, restarting
|
|
@@ -23260,17 +23397,40 @@ async function buildEnvironment(next, say) {
|
|
|
23260
23397
|
if (building || !installs() || next.templateId === null)
|
|
23261
23398
|
return;
|
|
23262
23399
|
const built = await readEnvironment();
|
|
23263
|
-
|
|
23400
|
+
const buildsSteps = buildIsDue(built, next.templateId);
|
|
23401
|
+
const clonesRepos = cloneIsDue(built, next.repos);
|
|
23402
|
+
if (!buildsSteps && !clonesRepos)
|
|
23264
23403
|
return;
|
|
23265
23404
|
building = true;
|
|
23266
23405
|
try {
|
|
23267
|
-
|
|
23268
|
-
|
|
23269
|
-
|
|
23406
|
+
let results = built?.results ?? [];
|
|
23407
|
+
let repos = built?.repos ?? [];
|
|
23408
|
+
if (buildsSteps) {
|
|
23409
|
+
say?.(ui.heading(`Building the ${next.steps.length} setup scripts your team runs on every box`));
|
|
23410
|
+
results = await setup.runSteps({ steps: next.steps, say });
|
|
23411
|
+
}
|
|
23412
|
+
if (clonesRepos) {
|
|
23413
|
+
say?.(ui.heading(`Cloning the ${next.repos.length} repos your team works in, into ${setup.workDir()}`));
|
|
23414
|
+
repos = await setup.cloneRepos({ repos: next.repos, say });
|
|
23415
|
+
await acceptClaudeGates(["craftspace"], repos.filter((repo) => repo.ok).map((repo) => repo.path));
|
|
23416
|
+
}
|
|
23417
|
+
await writeEnvironment({ templateId: next.templateId, results, repos, at: Date.now() });
|
|
23270
23418
|
} finally {
|
|
23271
23419
|
building = false;
|
|
23272
23420
|
}
|
|
23273
23421
|
}
|
|
23422
|
+
function cloneIsDue(built, wanted) {
|
|
23423
|
+
const cloned = built?.repos ?? [];
|
|
23424
|
+
if (wanted.length === 0 && cloned.length === 0)
|
|
23425
|
+
return false;
|
|
23426
|
+
if (wanted.length !== cloned.length)
|
|
23427
|
+
return true;
|
|
23428
|
+
const missing = wanted.some((repo) => !cloned.some((seen) => seen.name === repo.name && seen.ref === repo.ref && seen.ok));
|
|
23429
|
+
return missing || Date.now() - (built?.at ?? 0) > REBUILD_AFTER_MS;
|
|
23430
|
+
}
|
|
23431
|
+
function startIn(built) {
|
|
23432
|
+
return built?.repos.find((repo) => repo.ok)?.path ?? os5.homedir();
|
|
23433
|
+
}
|
|
23274
23434
|
function buildIsDue(built, templateId) {
|
|
23275
23435
|
if (built === null || built.templateId !== templateId)
|
|
23276
23436
|
return true;
|
|
@@ -23286,7 +23446,7 @@ async function readEnvironment() {
|
|
|
23286
23446
|
return parsed.success ? parsed.data : null;
|
|
23287
23447
|
}
|
|
23288
23448
|
async function writeEnvironment(built) {
|
|
23289
|
-
await
|
|
23449
|
+
await mkdir2(machine.home(), { recursive: true, mode: 448 });
|
|
23290
23450
|
await writeFile(environmentPath(), JSON.stringify(built, null, 2), { mode: 384 });
|
|
23291
23451
|
}
|
|
23292
23452
|
function installs() {
|
|
@@ -23363,7 +23523,7 @@ async function ensureKey() {
|
|
|
23363
23523
|
const existing = await readFile2(pub, "utf8").catch(() => null);
|
|
23364
23524
|
if (existing !== null)
|
|
23365
23525
|
return existing.trim();
|
|
23366
|
-
await
|
|
23526
|
+
await mkdir2(machine.home(), { recursive: true, mode: 448 });
|
|
23367
23527
|
await run2("ssh-keygen", ["-t", "ed25519", "-N", "", "-q", "-f", priv, "-C", `craftspace-${os5.hostname()}`]);
|
|
23368
23528
|
return (await readFile2(pub, "utf8")).trim();
|
|
23369
23529
|
}
|
|
@@ -23403,7 +23563,7 @@ ${KEYS_END}
|
|
|
23403
23563
|
const next = `${trimEnd(before)}${trimEnd(before) === "" ? "" : "\n"}${block}${after.replace(/^\n/, "")}`;
|
|
23404
23564
|
if (next === current)
|
|
23405
23565
|
return;
|
|
23406
|
-
await
|
|
23566
|
+
await mkdir2(path4.dirname(target), { recursive: true, mode: 448 });
|
|
23407
23567
|
await writeFile(target, next, { mode: 384 });
|
|
23408
23568
|
}
|
|
23409
23569
|
function trimEnd(text) {
|
|
@@ -23414,7 +23574,7 @@ async function writeAgentConfigs({ url: url2, token, writes }) {
|
|
|
23414
23574
|
const owned = [];
|
|
23415
23575
|
for (const write of planned) {
|
|
23416
23576
|
const target = expand(write.path);
|
|
23417
|
-
await
|
|
23577
|
+
await mkdir2(path4.dirname(target), { recursive: true });
|
|
23418
23578
|
const current = await readFile2(target, "utf8").catch(() => null);
|
|
23419
23579
|
if (current !== null)
|
|
23420
23580
|
await writeFile(`${target}.craftspace-backup`, current, { flag: "wx" }).catch(() => void 0);
|
|
@@ -23429,22 +23589,29 @@ async function writeAgentConfigs({ url: url2, token, writes }) {
|
|
|
23429
23589
|
function serversIn(writes) {
|
|
23430
23590
|
return writes.flatMap((write) => isPlainObject3(write.contents.mcpServers) ? Object.keys(write.contents.mcpServers) : []);
|
|
23431
23591
|
}
|
|
23432
|
-
async function acceptClaudeGates(servers) {
|
|
23592
|
+
async function acceptClaudeGates(servers, dirs = [os5.homedir()]) {
|
|
23433
23593
|
const target = path4.join(os5.homedir(), ".claude.json");
|
|
23434
23594
|
const raw = await readFile2(target, "utf8").catch(() => null);
|
|
23435
23595
|
if (raw === null)
|
|
23436
23596
|
return;
|
|
23437
23597
|
const parsed = JSON.parse(raw);
|
|
23438
23598
|
const projects = isPlainObject3(parsed.projects) ? parsed.projects : {};
|
|
23439
|
-
const
|
|
23440
|
-
|
|
23441
|
-
const
|
|
23442
|
-
|
|
23599
|
+
const opened = { ...projects };
|
|
23600
|
+
let moved = false;
|
|
23601
|
+
for (const dir of dirs) {
|
|
23602
|
+
const found = opened[dir];
|
|
23603
|
+
const here2 = isPlainObject3(found) ? { ...found } : {};
|
|
23604
|
+
const enabled = /* @__PURE__ */ new Set([...asArray(here2.enabledMcpjsonServers), ...servers]);
|
|
23605
|
+
if (here2.hasTrustDialogAccepted === true && enabled.size === asArray(here2.enabledMcpjsonServers).length)
|
|
23606
|
+
continue;
|
|
23607
|
+
here2.hasTrustDialogAccepted = true;
|
|
23608
|
+
here2.enabledMcpjsonServers = [...enabled];
|
|
23609
|
+
opened[dir] = here2;
|
|
23610
|
+
moved = true;
|
|
23611
|
+
}
|
|
23612
|
+
if (!moved)
|
|
23443
23613
|
return;
|
|
23444
|
-
|
|
23445
|
-
here2.enabledMcpjsonServers = [...enabled];
|
|
23446
|
-
const next = { ...parsed, projects: { ...projects, [os5.homedir()]: here2 } };
|
|
23447
|
-
await writeFile(target, `${JSON.stringify(next, null, 2)}
|
|
23614
|
+
await writeFile(target, `${JSON.stringify({ ...parsed, projects: opened }, null, 2)}
|
|
23448
23615
|
`, { mode: 384 });
|
|
23449
23616
|
}
|
|
23450
23617
|
function asArray(value) {
|
|
@@ -23495,16 +23662,17 @@ async function installService() {
|
|
|
23495
23662
|
return;
|
|
23496
23663
|
if (process.platform === "linux") {
|
|
23497
23664
|
const dir = path4.join(os5.homedir(), ".config", "systemd", "user");
|
|
23498
|
-
await
|
|
23665
|
+
await mkdir2(dir, { recursive: true });
|
|
23499
23666
|
await writeFile(path4.join(dir, `${SERVICE_NAME}.service`), systemdUnit());
|
|
23500
23667
|
await run2("loginctl", ["enable-linger", os5.userInfo().username]).catch(() => void 0);
|
|
23501
23668
|
await run2("systemctl", ["--user", "daemon-reload"]).catch(() => void 0);
|
|
23502
|
-
await run2("systemctl", ["--user", "enable",
|
|
23669
|
+
await run2("systemctl", ["--user", "enable", SERVICE_NAME]).catch(() => void 0);
|
|
23670
|
+
await run2("systemctl", ["--user", "restart", SERVICE_NAME]).catch(() => void 0);
|
|
23503
23671
|
return;
|
|
23504
23672
|
}
|
|
23505
23673
|
if (process.platform === "darwin") {
|
|
23506
23674
|
const target = path4.join(os5.homedir(), "Library", "LaunchAgents", `${LAUNCH_LABEL}.plist`);
|
|
23507
|
-
await
|
|
23675
|
+
await mkdir2(path4.dirname(target), { recursive: true });
|
|
23508
23676
|
await writeFile(target, launchdPlist());
|
|
23509
23677
|
await run2("launchctl", ["unload", target]).catch(() => void 0);
|
|
23510
23678
|
await run2("launchctl", ["load", target]).catch(() => void 0);
|
|
@@ -23600,6 +23768,7 @@ var ConfigSchema = external_exports.object({ url: external_exports.string(), mac
|
|
|
23600
23768
|
var BuiltSchema = external_exports.object({
|
|
23601
23769
|
templateId: external_exports.string(),
|
|
23602
23770
|
results: external_exports.array(WorkstationStepResultSchema),
|
|
23771
|
+
repos: external_exports.array(MachineRepoSchema).default([]),
|
|
23603
23772
|
at: external_exports.number()
|
|
23604
23773
|
});
|
|
23605
23774
|
var OwnedSchema = external_exports.object({ paths: external_exports.array(external_exports.string()) });
|
|
@@ -23611,6 +23780,7 @@ var MAX_BACKOFF_MS = 12e4;
|
|
|
23611
23780
|
var KEYS_BEGIN = "# craftspace begin";
|
|
23612
23781
|
var KEYS_END = "# craftspace end";
|
|
23613
23782
|
var UPDATE_EVERY_MS = 6 * 60 * 60 * 1e3;
|
|
23783
|
+
var UPDATE_AFTER_BEAT_MS = 5 * 60 * 1e3;
|
|
23614
23784
|
var BEATING = "beating every 15s, outbound only, and keeps this set up";
|
|
23615
23785
|
var PORTABLE_TERMS = /* @__PURE__ */ new Set([
|
|
23616
23786
|
"ansi",
|
|
@@ -23631,6 +23801,7 @@ var LINGER_LINE = `sudo loginctl enable-linger ${os5.userInfo().username}`;
|
|
|
23631
23801
|
var DEFAULT_URL = process.env.CRAFTSPACE_URL ?? "https://craftspace.app";
|
|
23632
23802
|
var PROBE_EVERY_MS = 3e5;
|
|
23633
23803
|
var REBUILD_AFTER_MS = 216e5;
|
|
23804
|
+
var updateWanted = null;
|
|
23634
23805
|
var probed = null;
|
|
23635
23806
|
var building = false;
|
|
23636
23807
|
var reports = /* @__PURE__ */ new Map();
|
package/dist/machine.js
CHANGED
|
@@ -3,14 +3,14 @@ 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';
|
|
10
10
|
import { runner } from './run.js';
|
|
11
11
|
import { setup } from './setup.js';
|
|
12
12
|
import { ui } from './ui.js';
|
|
13
|
-
import { update } from './update.js';
|
|
13
|
+
import { isNewer, update } from './update.js';
|
|
14
14
|
const run = promisify(execFile);
|
|
15
15
|
export const machine = {
|
|
16
16
|
home() {
|
|
@@ -89,10 +89,14 @@ 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,
|
|
95
97
|
});
|
|
98
|
+
if (answer.cli !== null && isNewer(answer.cli.version))
|
|
99
|
+
updateWanted = answer.cli.version;
|
|
96
100
|
await writeAuthorizedKeys(answer.authorizedKeys);
|
|
97
101
|
const building = buildEnvironment(answer.environment, say);
|
|
98
102
|
if (say === undefined)
|
|
@@ -104,7 +108,7 @@ export const machine = {
|
|
|
104
108
|
continue;
|
|
105
109
|
running.add(work.id);
|
|
106
110
|
void runner
|
|
107
|
-
.unattended({ work, onProgress: report })
|
|
111
|
+
.unattended({ work, cwd: startIn(built), onProgress: report })
|
|
108
112
|
.then(report)
|
|
109
113
|
.catch((error) => report({ id: work.id, state: 'failed', output: messageOf(error) }))
|
|
110
114
|
.finally(() => running.delete(work.id));
|
|
@@ -116,8 +120,10 @@ export const machine = {
|
|
|
116
120
|
let busy = false;
|
|
117
121
|
let checkedAt = 0;
|
|
118
122
|
for (;;) {
|
|
119
|
-
|
|
123
|
+
const asked = updateWanted !== null && Date.now() - checkedAt > UPDATE_AFTER_BEAT_MS;
|
|
124
|
+
if (!busy && (asked || failures > 2 || Date.now() - checkedAt > UPDATE_EVERY_MS)) {
|
|
120
125
|
checkedAt = Date.now();
|
|
126
|
+
updateWanted = null;
|
|
121
127
|
const moved = await update.toLatest();
|
|
122
128
|
if (moved !== null) {
|
|
123
129
|
process.stderr.write(`craftspace ${CLI_VERSION} -> ${moved}, restarting\n`);
|
|
@@ -238,18 +244,41 @@ async function buildEnvironment(next, say) {
|
|
|
238
244
|
if (building || !installs() || next.templateId === null)
|
|
239
245
|
return;
|
|
240
246
|
const built = await readEnvironment();
|
|
241
|
-
|
|
247
|
+
const buildsSteps = buildIsDue(built, next.templateId);
|
|
248
|
+
const clonesRepos = cloneIsDue(built, next.repos);
|
|
249
|
+
if (!buildsSteps && !clonesRepos)
|
|
242
250
|
return;
|
|
243
251
|
building = true;
|
|
244
252
|
try {
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
253
|
+
let results = built?.results ?? [];
|
|
254
|
+
let repos = built?.repos ?? [];
|
|
255
|
+
if (buildsSteps) {
|
|
256
|
+
say?.(ui.heading(`Building the ${next.steps.length} setup scripts your team runs on every box`));
|
|
257
|
+
results = await setup.runSteps({ steps: next.steps, say });
|
|
258
|
+
}
|
|
259
|
+
if (clonesRepos) {
|
|
260
|
+
say?.(ui.heading(`Cloning the ${next.repos.length} repos your team works in, into ${setup.workDir()}`));
|
|
261
|
+
repos = await setup.cloneRepos({ repos: next.repos, say });
|
|
262
|
+
await acceptClaudeGates(['craftspace'], repos.filter((repo) => repo.ok).map((repo) => repo.path));
|
|
263
|
+
}
|
|
264
|
+
await writeEnvironment({ templateId: next.templateId, results, repos, at: Date.now() });
|
|
248
265
|
}
|
|
249
266
|
finally {
|
|
250
267
|
building = false;
|
|
251
268
|
}
|
|
252
269
|
}
|
|
270
|
+
function cloneIsDue(built, wanted) {
|
|
271
|
+
const cloned = built?.repos ?? [];
|
|
272
|
+
if (wanted.length === 0 && cloned.length === 0)
|
|
273
|
+
return false;
|
|
274
|
+
if (wanted.length !== cloned.length)
|
|
275
|
+
return true;
|
|
276
|
+
const missing = wanted.some((repo) => !cloned.some((seen) => seen.name === repo.name && seen.ref === repo.ref && seen.ok));
|
|
277
|
+
return missing || Date.now() - (built?.at ?? 0) > REBUILD_AFTER_MS;
|
|
278
|
+
}
|
|
279
|
+
function startIn(built) {
|
|
280
|
+
return built?.repos.find((repo) => repo.ok)?.path ?? os.homedir();
|
|
281
|
+
}
|
|
253
282
|
function buildIsDue(built, templateId) {
|
|
254
283
|
if (built === null || built.templateId !== templateId)
|
|
255
284
|
return true;
|
|
@@ -403,22 +432,29 @@ async function writeAgentConfigs({ url, token, writes, }) {
|
|
|
403
432
|
function serversIn(writes) {
|
|
404
433
|
return writes.flatMap((write) => isPlainObject(write.contents.mcpServers) ? Object.keys(write.contents.mcpServers) : []);
|
|
405
434
|
}
|
|
406
|
-
async function acceptClaudeGates(servers) {
|
|
435
|
+
async function acceptClaudeGates(servers, dirs = [os.homedir()]) {
|
|
407
436
|
const target = path.join(os.homedir(), '.claude.json');
|
|
408
437
|
const raw = await readFile(target, 'utf8').catch(() => null);
|
|
409
438
|
if (raw === null)
|
|
410
439
|
return;
|
|
411
440
|
const parsed = JSON.parse(raw);
|
|
412
441
|
const projects = isPlainObject(parsed.projects) ? parsed.projects : {};
|
|
413
|
-
const
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
442
|
+
const opened = { ...projects };
|
|
443
|
+
let moved = false;
|
|
444
|
+
for (const dir of dirs) {
|
|
445
|
+
const found = opened[dir];
|
|
446
|
+
const here = isPlainObject(found) ? { ...found } : {};
|
|
447
|
+
const enabled = new Set([...asArray(here.enabledMcpjsonServers), ...servers]);
|
|
448
|
+
if (here.hasTrustDialogAccepted === true && enabled.size === asArray(here.enabledMcpjsonServers).length)
|
|
449
|
+
continue;
|
|
450
|
+
here.hasTrustDialogAccepted = true;
|
|
451
|
+
here.enabledMcpjsonServers = [...enabled];
|
|
452
|
+
opened[dir] = here;
|
|
453
|
+
moved = true;
|
|
454
|
+
}
|
|
455
|
+
if (!moved)
|
|
417
456
|
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 });
|
|
457
|
+
await writeFile(target, `${JSON.stringify({ ...parsed, projects: opened }, null, 2)}\n`, { mode: 0o600 });
|
|
422
458
|
}
|
|
423
459
|
function asArray(value) {
|
|
424
460
|
return Array.isArray(value) ? value : [];
|
|
@@ -472,7 +508,8 @@ async function installService() {
|
|
|
472
508
|
await writeFile(path.join(dir, `${SERVICE_NAME}.service`), systemdUnit());
|
|
473
509
|
await run('loginctl', ['enable-linger', os.userInfo().username]).catch(() => undefined);
|
|
474
510
|
await run('systemctl', ['--user', 'daemon-reload']).catch(() => undefined);
|
|
475
|
-
await run('systemctl', ['--user', 'enable',
|
|
511
|
+
await run('systemctl', ['--user', 'enable', SERVICE_NAME]).catch(() => undefined);
|
|
512
|
+
await run('systemctl', ['--user', 'restart', SERVICE_NAME]).catch(() => undefined);
|
|
476
513
|
return;
|
|
477
514
|
}
|
|
478
515
|
if (process.platform === 'darwin') {
|
|
@@ -573,6 +610,7 @@ const ConfigSchema = z.object({ url: z.string(), machineId: z.string() });
|
|
|
573
610
|
const BuiltSchema = z.object({
|
|
574
611
|
templateId: z.string(),
|
|
575
612
|
results: z.array(WorkstationStepResultSchema),
|
|
613
|
+
repos: z.array(MachineRepoSchema).default([]),
|
|
576
614
|
at: z.number(),
|
|
577
615
|
});
|
|
578
616
|
const OwnedSchema = z.object({ paths: z.array(z.string()) });
|
|
@@ -584,6 +622,7 @@ const MAX_BACKOFF_MS = 120_000;
|
|
|
584
622
|
const KEYS_BEGIN = '# craftspace begin';
|
|
585
623
|
const KEYS_END = '# craftspace end';
|
|
586
624
|
const UPDATE_EVERY_MS = 6 * 60 * 60 * 1_000;
|
|
625
|
+
const UPDATE_AFTER_BEAT_MS = 5 * 60 * 1_000;
|
|
587
626
|
const BEATING = 'beating every 15s, outbound only, and keeps this set up';
|
|
588
627
|
const PORTABLE_TERMS = new Set([
|
|
589
628
|
'ansi',
|
|
@@ -604,6 +643,7 @@ const LINGER_LINE = `sudo loginctl enable-linger ${os.userInfo().username}`;
|
|
|
604
643
|
const DEFAULT_URL = process.env.CRAFTSPACE_URL ?? 'https://craftspace.app';
|
|
605
644
|
const PROBE_EVERY_MS = 300_000;
|
|
606
645
|
const REBUILD_AFTER_MS = 21_600_000;
|
|
646
|
+
let updateWanted = null;
|
|
607
647
|
let probed = null;
|
|
608
648
|
let building = false;
|
|
609
649
|
const reports = new Map();
|
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;
|