@akanjs/devkit 3.1.0-rc.1 → 3.1.0-rc.2
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/akanConfig/akanConfig.test.ts +51 -5
- package/akanConfig/akanConfig.ts +31 -12
- package/akanConfig/akanWorkspaceConfig.test.ts +6 -6
- package/akanConfig/akanWorkspaceConfig.ts +1 -1
- package/applicationBuildRunner.test.ts +33 -1
- package/applicationBuildRunner.ts +4 -5
- package/cloud/cloudApi.ts +22 -15
- package/cloud/cloudHttpError.test.ts +32 -0
- package/cloud/cloudHttpError.ts +54 -0
- package/cloud/cloudUpdates.ts +138 -0
- package/cloud/globalConfig.test.ts +17 -0
- package/cloud/globalConfig.ts +8 -2
- package/cloud/index.ts +3 -0
- package/cloud/pipelineSecrets.ts +114 -0
- package/cloud/updatePackUpload.test.ts +100 -45
- package/cloud/updatePackUpload.ts +38 -18
- package/commandDecorators/argMeta.ts +11 -2
- package/commandDecorators/command.ts +40 -6
- package/commandDecorators/commandBuilder.ts +6 -1
- package/commandDecorators/commandDecorators.test.ts +205 -4
- package/commandDecorators/exitCodeError.ts +15 -0
- package/commandDecorators/index.ts +1 -0
- package/executors.test.ts +46 -0
- package/executors.ts +26 -23
- package/frontendBuild/csrArtifactBuilder.ts +18 -1
- package/incrementalBuilder/buildBatch.proc.ts +12 -7
- package/incrementalBuilder/incrementalBuilder.proc.test.ts +63 -0
- package/incrementalBuilder/incrementalBuilder.proc.ts +7 -5
- package/mobile/desktopServerStage.test.ts +13 -0
- package/mobile/desktopServerStage.ts +5 -7
- package/mobile/index.ts +2 -0
- package/mobile/nativeApi.test.ts +26 -0
- package/mobile/nativeApi.ts +5 -2
- package/mobile/nativeApp.test.ts +135 -33
- package/mobile/nativeApp.ts +109 -73
- package/mobile/nativeConfig.ts +2 -1
- package/mobile/nativePublicEnv.test.ts +131 -0
- package/mobile/nativePublicEnv.ts +116 -0
- package/mobile/serverBundleSecrets.test.ts +79 -0
- package/mobile/serverBundleSecrets.ts +60 -16
- package/mobile/updatePackFolder.test.ts +98 -0
- package/mobile/updatePackFolder.ts +131 -0
- package/mobile/updateUploader.test.ts +114 -22
- package/mobile/updateUploader.ts +75 -8
- package/package.json +2 -2
- package/pipeline/PipelineCompiler.test.ts +157 -1
- package/pipeline/PipelineCompiler.ts +80 -46
- package/pipeline/PipelineDefinition.test.ts +9 -0
- package/pipeline/PipelineDefinition.ts +4 -3
- package/pipeline/PipelineDryRun.test.ts +27 -0
- package/pipeline/PipelineDryRun.ts +2 -2
- package/pipeline/PipelineInputReader.ts +157 -253
- package/pipeline/PipelineRules.ts +92 -0
- package/pipeline/index.ts +1 -0
- package/qualityScanner.test.ts +5 -3
- package/slicePlanner.ts +5 -3
- package/subspace.test.ts +90 -19
- package/subspace.ts +52 -500
- package/subspaceClone.ts +361 -0
- package/subspaceConfig.ts +1 -2
- package/subspaceReport.ts +162 -0
- package/transforms/serverEnvPlugin.test.ts +76 -0
- package/transforms/serverEnvPlugin.ts +9 -25
- package/transforms/serverEnvProfile.test.ts +189 -0
- package/transforms/serverEnvProfile.ts +324 -60
- package/typecheck/projectTypecheck.ts +6 -5
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
PipelineActionName,
|
|
3
|
+
PipelineMilestone,
|
|
4
|
+
PipelineOtaPlatform,
|
|
5
|
+
PipelinePlanRetry,
|
|
6
|
+
PipelineTrigger,
|
|
7
|
+
} from "akanjs/common";
|
|
8
|
+
|
|
9
|
+
export interface PipelineWhenRule {
|
|
10
|
+
branch: string[] | null;
|
|
11
|
+
trigger: PipelineTrigger[] | null;
|
|
12
|
+
changed: string[] | null;
|
|
13
|
+
/** A workspace `matrix: "apps"` stage's narrowing; never set elsewhere. */
|
|
14
|
+
app: string[] | null;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface PipelineStepRule {
|
|
18
|
+
localId: string;
|
|
19
|
+
name: string;
|
|
20
|
+
timeoutSeconds: number | null;
|
|
21
|
+
retry: PipelinePlanRetry | null;
|
|
22
|
+
allowFailure: boolean;
|
|
23
|
+
run: string;
|
|
24
|
+
cwd: string;
|
|
25
|
+
/** The stage's env under the step's own. */
|
|
26
|
+
env: Record<string, string>;
|
|
27
|
+
secrets: string[];
|
|
28
|
+
skipExitCodes: number[];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface PipelineStageRuleBase {
|
|
32
|
+
id: string;
|
|
33
|
+
name: string;
|
|
34
|
+
description: string | null;
|
|
35
|
+
milestone: PipelineMilestone | null;
|
|
36
|
+
when: PipelineWhenRule;
|
|
37
|
+
timeoutSeconds: number | null;
|
|
38
|
+
allowFailure: boolean;
|
|
39
|
+
path: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface PipelineShellStageRule extends PipelineStageRuleBase {
|
|
43
|
+
kind: "sh";
|
|
44
|
+
steps: PipelineStepRule[];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface PipelineActionStageRule extends PipelineStageRuleBase {
|
|
48
|
+
kind: "action";
|
|
49
|
+
action: PipelineActionName;
|
|
50
|
+
named: boolean;
|
|
51
|
+
retry: PipelinePlanRetry | null;
|
|
52
|
+
/** `ota` only; `null` takes the default. */
|
|
53
|
+
targets: string[] | null;
|
|
54
|
+
platforms: PipelineOtaPlatform[] | null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface PipelineParallelStageRule extends PipelineStageRuleBase {
|
|
58
|
+
kind: "parallel";
|
|
59
|
+
maxParallel: number | null;
|
|
60
|
+
failFast: boolean;
|
|
61
|
+
branches: PipelineShellStageRule[];
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface PipelineMatrixStageRule extends PipelineStageRuleBase {
|
|
65
|
+
kind: "matrix";
|
|
66
|
+
maxParallel: number | null;
|
|
67
|
+
steps: PipelineStepRule[];
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export type PipelineWorkspaceStageRule = PipelineShellStageRule | PipelineParallelStageRule | PipelineMatrixStageRule;
|
|
71
|
+
export type PipelineAppStageRule = PipelineShellStageRule | PipelineActionStageRule;
|
|
72
|
+
|
|
73
|
+
export interface PipelinePostRule {
|
|
74
|
+
success: PipelineStepRule[];
|
|
75
|
+
failure: PipelineStepRule[];
|
|
76
|
+
canceled: PipelineStepRule[];
|
|
77
|
+
always: PipelineStepRule[];
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export interface PipelineWorkspaceRule {
|
|
81
|
+
env: Record<string, string>;
|
|
82
|
+
timeoutSeconds: number | null;
|
|
83
|
+
before: PipelineWorkspaceStageRule[];
|
|
84
|
+
after: PipelineWorkspaceStageRule[];
|
|
85
|
+
post: PipelinePostRule;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export interface PipelineAppRule {
|
|
89
|
+
env: Record<string, string>;
|
|
90
|
+
after: string[];
|
|
91
|
+
stages: PipelineAppStageRule[];
|
|
92
|
+
}
|
package/pipeline/index.ts
CHANGED
package/qualityScanner.test.ts
CHANGED
|
@@ -243,7 +243,7 @@ describe("AkanQualityScanner layout rules", () => {
|
|
|
243
243
|
|
|
244
244
|
const warnings = rulesOf(await new AkanQualityScanner().scan(root), "akan.layout.lib-facet-file");
|
|
245
245
|
|
|
246
|
-
expect(warnings.map((warning) => warning.file).sort()).toEqual([
|
|
246
|
+
expect(warnings.map((warning) => warning.file ?? "").sort((a, b) => a.localeCompare(b))).toEqual([
|
|
247
247
|
"libs/demo/lib/helper.ts",
|
|
248
248
|
"libs/demo/lib/user.signal.test.ts",
|
|
249
249
|
]);
|
|
@@ -302,7 +302,7 @@ describe("bang comments in browser-reachable files", () => {
|
|
|
302
302
|
"apps/demo/ui/Last.tsx": "export const Last = () => null;\n//! left behind\n",
|
|
303
303
|
});
|
|
304
304
|
|
|
305
|
-
expect(warnings.map((warning) => `${warning.file}:${warning.line}`).sort()).toEqual([
|
|
305
|
+
expect(warnings.map((warning) => `${warning.file}:${warning.line}`).sort((a, b) => a.localeCompare(b))).toEqual([
|
|
306
306
|
"apps/demo/ui/First.tsx:1",
|
|
307
307
|
"apps/demo/ui/Last.tsx:2",
|
|
308
308
|
]);
|
|
@@ -350,6 +350,8 @@ describe("bang comments in browser-reachable files", () => {
|
|
|
350
350
|
];
|
|
351
351
|
const warnings = await bangWarningsIn(Object.fromEntries(files.map((file) => [file, body])));
|
|
352
352
|
|
|
353
|
-
expect(warnings.map((warning) => warning.file).sort()).toEqual(
|
|
353
|
+
expect(warnings.map((warning) => warning.file ?? "").sort((a, b) => a.localeCompare(b))).toEqual(
|
|
354
|
+
[...files].sort((a, b) => a.localeCompare(b)),
|
|
355
|
+
);
|
|
354
356
|
});
|
|
355
357
|
});
|
package/slicePlanner.ts
CHANGED
|
@@ -51,6 +51,10 @@ export class SlicePlanner {
|
|
|
51
51
|
return found ? peers : null;
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
static rootEntriesOf(rootFiles: string[]) {
|
|
55
|
+
return [...new Set(rootFiles.map((file) => file.split("/")[0] ?? file))].sort();
|
|
56
|
+
}
|
|
57
|
+
|
|
54
58
|
/** DefinitelyTyped's name for a package: `lodash` -> `@types/lodash`, `@scope/x` -> `@types/scope__x`. */
|
|
55
59
|
static typesPackageOf(dependency: string) {
|
|
56
60
|
return dependency.startsWith("@") ? `@types/${dependency.slice(1).replace("/", "__")}` : `@types/${dependency}`;
|
|
@@ -191,8 +195,6 @@ export class SlicePlanner {
|
|
|
191
195
|
}
|
|
192
196
|
}
|
|
193
197
|
|
|
194
|
-
const rootEntriesOf = (rootFiles: string[]) => [...new Set(rootFiles.map((file) => file.split("/")[0] ?? file))].sort();
|
|
195
|
-
|
|
196
198
|
export function formatSlicePlan(plan: SlicePlan) {
|
|
197
199
|
const libCounts = Object.entries(plan.libFiles).map(([lib, files]) => ` - libs/${lib}: ${files.length} files`);
|
|
198
200
|
const sections = [
|
|
@@ -200,7 +202,7 @@ export function formatSlicePlan(plan: SlicePlan) {
|
|
|
200
202
|
`app: ${plan.app} (apps/${plan.app}: ${plan.appFiles.length} files)`,
|
|
201
203
|
`libs: ${plan.libs.length ? plan.libs.join(", ") : "(none)"}`,
|
|
202
204
|
...libCounts,
|
|
203
|
-
`workspace shell: ${plan.rootFiles.length} files across ${rootEntriesOf(plan.rootFiles).join(", ")}`,
|
|
205
|
+
`workspace shell: ${plan.rootFiles.length} files across ${SlicePlanner.rootEntriesOf(plan.rootFiles).join(", ")}`,
|
|
204
206
|
"",
|
|
205
207
|
`Root dependencies this slice does not use (${plan.unusedDependencies.length}) — pruned:`,
|
|
206
208
|
"",
|
package/subspace.test.ts
CHANGED
|
@@ -3,14 +3,10 @@ import { mkdir, readdir, rm } from "node:fs/promises";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { Executor, WorkspaceExecutor } from "./executors";
|
|
5
5
|
import { FileSys } from "./fileSys";
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
formatSubspacePlan,
|
|
9
|
-
formatSubspacePushResults,
|
|
10
|
-
formatSubspaceStatuses,
|
|
11
|
-
Subspace,
|
|
12
|
-
} from "./subspace";
|
|
6
|
+
import { Subspace } from "./subspace";
|
|
7
|
+
import { SubspaceClone } from "./subspaceClone";
|
|
13
8
|
import { SubspaceConfig } from "./subspaceConfig";
|
|
9
|
+
import { SubspaceReport } from "./subspaceReport";
|
|
14
10
|
import { isolateEnv, tempDirs, writeText as write } from "./testHelpers";
|
|
15
11
|
|
|
16
12
|
//? Each case drives several real git clones and pushes; on a 4-core Windows VM that alone lands at 2-5s.
|
|
@@ -189,7 +185,7 @@ describe("Subspace push", () => {
|
|
|
189
185
|
await subspace.push("develop", { verify: false });
|
|
190
186
|
const subspaceRoot = await cloneSubspace(workRoot, bareRoot, "check");
|
|
191
187
|
|
|
192
|
-
const anchor = (await FileSys.readJson(path.join(subspaceRoot,
|
|
188
|
+
const anchor = (await FileSys.readJson(path.join(subspaceRoot, SubspaceClone.anchorFile))) as {
|
|
193
189
|
branch: string;
|
|
194
190
|
apps: string[];
|
|
195
191
|
};
|
|
@@ -257,7 +253,7 @@ describe("Subspace push", () => {
|
|
|
257
253
|
await git(customer, ["commit", "--quiet", "-m", "our own global stages"]);
|
|
258
254
|
await write(path.join(customer, "Jenkinsfile"), "pipeline { agent none }\n");
|
|
259
255
|
await git(customer, ["add", "-A"]);
|
|
260
|
-
await git(customer, ["commit", "--quiet", "-m", `${
|
|
256
|
+
await git(customer, ["commit", "--quiet", "-m", `${SubspaceClone.syncSubjectPrefix}workspace@0000000`]);
|
|
261
257
|
await git(customer, ["push", "--quiet", "origin", "develop"]);
|
|
262
258
|
|
|
263
259
|
expect((await subspace.push("develop", { verify: false })).outcome).toBe("pushed");
|
|
@@ -268,8 +264,8 @@ describe("Subspace push", () => {
|
|
|
268
264
|
expect(await FileSys.fileExists(path.join(subspaceRoot, "Jenkinsfile"))).toBe(false);
|
|
269
265
|
});
|
|
270
266
|
|
|
271
|
-
test("refuses a lock that resolves from a registry only this machine reaches", async () => {
|
|
272
|
-
const { wsRoot, subspace } = await makeMirror("lock-served", "lock-private", "lock-kit");
|
|
267
|
+
test("refuses a lock that resolves from a registry only this machine reaches, before it clones", async () => {
|
|
268
|
+
const { wsRoot, subspace, clonePath } = await makeMirror("lock-served", "lock-private", "lock-kit");
|
|
273
269
|
await write(
|
|
274
270
|
path.join(wsRoot, "bun.lock"),
|
|
275
271
|
'{ "packages": { "lodash": ["lodash@4.0.0", "http://127.0.0.1:4873/lodash/-/lodash-4.0.0.tgz"] } }\n',
|
|
@@ -279,6 +275,40 @@ describe("Subspace push", () => {
|
|
|
279
275
|
const result = (await subspace.push("develop", { verify: false })) as { outcome: string; reason?: string };
|
|
280
276
|
expect(result.outcome).toBe("refused");
|
|
281
277
|
expect(result.reason).toContain("http://127.0.0.1:4873");
|
|
278
|
+
expect(await FileSys.dirExists(clonePath)).toBe(false);
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
test("refuses a push and a pull while akan.subspace.ts is left at the workspace root", async () => {
|
|
282
|
+
const { wsRoot, workRoot, bareRoot, subspace } = await makeMirror("left-served", "left-private", "left-kit");
|
|
283
|
+
await write(
|
|
284
|
+
path.join(wsRoot, Subspace.retiredConfigFile),
|
|
285
|
+
'export default { subspaces: [{ name: "globex", repo: "git@example.com:globex/app.git", apps: ["left-private"] }] };\n',
|
|
286
|
+
);
|
|
287
|
+
await git(wsRoot, ["add", "-A"]);
|
|
288
|
+
await git(wsRoot, ["commit", "--quiet", "-m", "moved the declaration, kept the file"]);
|
|
289
|
+
|
|
290
|
+
const result = await subspace.push("develop", { verify: false });
|
|
291
|
+
expect(result.outcome).toBe("refused");
|
|
292
|
+
expect(result.reason).toContain("move it under `subspace` in akan.config.ts and delete it");
|
|
293
|
+
await expect(subspace.pull("develop")).rejects.toThrow("delete it");
|
|
294
|
+
const subspaceRoot = await cloneSubspace(workRoot, bareRoot, "left-check");
|
|
295
|
+
expect(await FileSys.fileExists(path.join(subspaceRoot, Subspace.retiredConfigFile))).toBe(false);
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
test("removes an akan.subspace.ts an earlier push left in the subspace", async () => {
|
|
299
|
+
const { workRoot, bareRoot, subspace } = await makeMirror("leaked-served", "leaked-private", "leaked-kit");
|
|
300
|
+
const customer = await cloneSubspace(workRoot, bareRoot, "leaked-customer");
|
|
301
|
+
await write(
|
|
302
|
+
path.join(customer, Subspace.retiredConfigFile),
|
|
303
|
+
'export default { subspaces: [{ name: "globex" }] };\n',
|
|
304
|
+
);
|
|
305
|
+
await git(customer, ["add", "-A"]);
|
|
306
|
+
await git(customer, ["commit", "--quiet", "-m", `${SubspaceClone.syncSubjectPrefix}workspace@0000000`]);
|
|
307
|
+
await git(customer, ["push", "--quiet", "origin", "develop"]);
|
|
308
|
+
|
|
309
|
+
expect((await subspace.push("develop", { verify: false })).outcome).toBe("pushed");
|
|
310
|
+
const subspaceRoot = await cloneSubspace(workRoot, bareRoot, "leaked-check");
|
|
311
|
+
expect(await FileSys.fileExists(path.join(subspaceRoot, Subspace.retiredConfigFile))).toBe(false);
|
|
282
312
|
});
|
|
283
313
|
|
|
284
314
|
test("refuses a branch the subspace does not have, and one the config does not allow", async () => {
|
|
@@ -319,7 +349,7 @@ describe("Subspace push", () => {
|
|
|
319
349
|
});
|
|
320
350
|
|
|
321
351
|
test("reports a refusal's whole reason, indented, instead of one truncated line", () => {
|
|
322
|
-
const report =
|
|
352
|
+
const report = SubspaceReport.pushResults([
|
|
323
353
|
{ name: "acme", outcome: "refused", reason: "verification failed\nexit code: 1", changedFiles: 12 },
|
|
324
354
|
{ name: "beta", outcome: "pushed", commit: "abc1234", changedFiles: 3 },
|
|
325
355
|
]);
|
|
@@ -341,7 +371,7 @@ describe("Subspace status", () => {
|
|
|
341
371
|
expect(status.driftedLibs).toEqual([]);
|
|
342
372
|
expect(status.anchor.commit).toMatch(/^[0-9a-f]{40}$/);
|
|
343
373
|
expect(status.anchor.incoming).toEqual([]);
|
|
344
|
-
expect(
|
|
374
|
+
expect(SubspaceReport.statuses([status])).toContain("up to date, no customer commits");
|
|
345
375
|
});
|
|
346
376
|
|
|
347
377
|
test("names the drifted library and counts the customer commits", async () => {
|
|
@@ -358,7 +388,7 @@ describe("Subspace status", () => {
|
|
|
358
388
|
|
|
359
389
|
expect(status.driftedLibs).toEqual(["sdrift-kit"]);
|
|
360
390
|
expect(status.anchor.incoming.map((commit) => commit.subject)).toEqual(["lib hack"]);
|
|
361
|
-
expect(
|
|
391
|
+
expect(SubspaceReport.statuses([status])).toContain("DRIFTED LIBS: sdrift-kit");
|
|
362
392
|
});
|
|
363
393
|
|
|
364
394
|
test("reports a subspace with no such branch instead of creating one", async () => {
|
|
@@ -366,7 +396,7 @@ describe("Subspace status", () => {
|
|
|
366
396
|
const status = await subspace.status("main");
|
|
367
397
|
|
|
368
398
|
expect(status.hasBranch).toBe(false);
|
|
369
|
-
expect(
|
|
399
|
+
expect(SubspaceReport.statuses([status])).toContain("no such branch");
|
|
370
400
|
});
|
|
371
401
|
});
|
|
372
402
|
|
|
@@ -389,7 +419,7 @@ describe("Subspace diff", () => {
|
|
|
389
419
|
//? Direction: the workspace would restore `helper = 1` and delete the file the subspace added.
|
|
390
420
|
expect(result.libs.patch).toContain("-export const helper = 99;");
|
|
391
421
|
expect(result.libs.patch).toContain("+export const helper = 1;");
|
|
392
|
-
expect(
|
|
422
|
+
expect(SubspaceReport.diff(result)).toContain("LIBRARY changes (1)");
|
|
393
423
|
});
|
|
394
424
|
|
|
395
425
|
test("is empty right after a push", async () => {
|
|
@@ -399,7 +429,7 @@ describe("Subspace diff", () => {
|
|
|
399
429
|
const result = await subspace.diff("develop");
|
|
400
430
|
expect(result.app.files).toEqual([]);
|
|
401
431
|
expect(result.libs.files).toEqual([]);
|
|
402
|
-
expect(
|
|
432
|
+
expect(SubspaceReport.diff(result)).toContain("identical to the workspace");
|
|
403
433
|
});
|
|
404
434
|
});
|
|
405
435
|
|
|
@@ -509,6 +539,16 @@ describe("SubspaceConfig", () => {
|
|
|
509
539
|
expect(() => config.assertPushable("feature/x")).toThrow(/not pushable/);
|
|
510
540
|
expect(() => config.assertPushable("develop")).not.toThrow();
|
|
511
541
|
});
|
|
542
|
+
|
|
543
|
+
test("names the setting that allows a branch when it refuses one", () => {
|
|
544
|
+
const config = new SubspaceConfig({
|
|
545
|
+
pushableBranches: ["main"],
|
|
546
|
+
subspaces: [{ name: "a", repo: "a.git", apps: ["one"] }],
|
|
547
|
+
});
|
|
548
|
+
expect(() => config.assertPushable("develop")).toThrow(
|
|
549
|
+
'Branch "develop" is not pushable. `subspace.pushableBranches` in the workspace root akan.config.ts allows: main',
|
|
550
|
+
);
|
|
551
|
+
});
|
|
512
552
|
});
|
|
513
553
|
|
|
514
554
|
describe("Subspace.plan", () => {
|
|
@@ -532,7 +572,7 @@ describe("Subspace.plan", () => {
|
|
|
532
572
|
expect(plan.files).toBeGreaterThan(0);
|
|
533
573
|
expect((await git(wsRoot, ["remote"])).trim()).toBe("");
|
|
534
574
|
expect(await FileSys.dirExists(clonePath)).toBe(false);
|
|
535
|
-
expect(
|
|
575
|
+
expect(SubspaceReport.plan(plan)).toContain("Nothing here would make a push refuse.");
|
|
536
576
|
});
|
|
537
577
|
|
|
538
578
|
test("names what a push would refuse: a branch it may not push and a loopback-registry lock", async () => {
|
|
@@ -545,7 +585,38 @@ describe("Subspace.plan", () => {
|
|
|
545
585
|
await git(wsRoot, ["commit", "--quiet", "-m", "lock"]);
|
|
546
586
|
const plan = await subspace.plan("main");
|
|
547
587
|
expect(plan.branchRefusal).toContain('Branch "main" is not pushable');
|
|
588
|
+
expect(plan.branchRefusal).toContain("`subspace.pushableBranches`");
|
|
548
589
|
expect(plan.lockProblem).toContain("http://127.0.0.1:4873");
|
|
549
|
-
expect(
|
|
590
|
+
expect(SubspaceReport.plan(plan)).toContain("A push would be refused:");
|
|
591
|
+
});
|
|
592
|
+
|
|
593
|
+
test("names a dirty tree as a blocker, as push refuses one, and judges HEAD's lock, the one a push ships", async () => {
|
|
594
|
+
const { wsRoot, subspace } = await makeMirror("served", "private", "kit");
|
|
595
|
+
await write(path.join(wsRoot, "bun.lock"), '{ "packages": {} }\n');
|
|
596
|
+
await git(wsRoot, ["add", "-f", "bun.lock"]);
|
|
597
|
+
await git(wsRoot, ["commit", "--quiet", "-m", "a public lock"]);
|
|
598
|
+
await write(
|
|
599
|
+
path.join(wsRoot, "bun.lock"),
|
|
600
|
+
'{ "packages": { "x": ["x@1.0.0", "http://127.0.0.1:4873/x/-/x-1.0.0.tgz"] } }\n',
|
|
601
|
+
);
|
|
602
|
+
|
|
603
|
+
const plan = await subspace.plan("develop");
|
|
604
|
+
|
|
605
|
+
expect(plan.workspaceRefusal).toBe("workspace working tree is dirty");
|
|
606
|
+
expect(plan.lockProblem).toBeNull();
|
|
607
|
+
expect(Subspace.planBlockers(plan)).toEqual(["workspace working tree is dirty"]);
|
|
608
|
+
expect(SubspaceReport.plan(plan)).toContain(" - workspace working tree is dirty");
|
|
609
|
+
});
|
|
610
|
+
|
|
611
|
+
test("names a leftover akan.subspace.ts as a blocker and never as an entry a push ships", async () => {
|
|
612
|
+
const { wsRoot, subspace } = await makeMirror("served", "private", "kit");
|
|
613
|
+
await write(path.join(wsRoot, Subspace.retiredConfigFile), "export default {};\n");
|
|
614
|
+
await git(wsRoot, ["add", "-A"]);
|
|
615
|
+
await git(wsRoot, ["commit", "--quiet", "-m", "kept the old declaration"]);
|
|
616
|
+
|
|
617
|
+
const plan = await subspace.plan("develop");
|
|
618
|
+
|
|
619
|
+
expect(plan.workspaceRefusal).toContain("delete it");
|
|
620
|
+
expect(plan.rootEntries).not.toContain(Subspace.retiredConfigFile);
|
|
550
621
|
});
|
|
551
622
|
});
|