@smoothbricks/cli 0.11.10 → 0.11.12
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/monorepo/cargo-policy.d.ts +25 -1
- package/dist/monorepo/cargo-policy.d.ts.map +1 -1
- package/dist/monorepo/cargo-policy.js +396 -4
- package/dist/monorepo/index.d.ts.map +1 -1
- package/dist/monorepo/index.js +5 -1
- package/dist/monorepo/packs/index.d.ts.map +1 -1
- package/dist/monorepo/packs/index.js +13 -0
- package/managed/raw/tooling/direnv/devenv.smoo.nix +6 -0
- package/managed/raw/tooling/direnv/github-actions-bootstrap.sh +80 -21
- package/managed/templates/github/actions/setup-devenv/action.yml +33 -7
- package/package.json +2 -2
- package/src/monorepo/cargo-policy.test.ts +206 -3
- package/src/monorepo/cargo-policy.ts +341 -2
- package/src/monorepo/github-actions-bootstrap.test.ts +65 -11
- package/src/monorepo/index.ts +5 -1
- package/src/monorepo/packs/index.test.ts +32 -0
- package/src/monorepo/packs/index.ts +13 -0
|
@@ -1,2 +1,26 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* The one command this policy shells out to, behind a seam. `cargo hakari
|
|
3
|
+
* verify` is the only authority on whether a generated workspace-hack still
|
|
4
|
+
* unifies what the workspace resolves today; nothing in these files can answer
|
|
5
|
+
* that. Tests supply their own so the policy stays runnable without the binary.
|
|
6
|
+
*/
|
|
7
|
+
export interface CargoHakariShell {
|
|
8
|
+
run(directory: string, args: readonly string[]): {
|
|
9
|
+
code: number;
|
|
10
|
+
output: string;
|
|
11
|
+
missing: boolean;
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
export interface CargoPolicyOptions {
|
|
15
|
+
/** Injected in tests; production shells out to the real `cargo hakari`. */
|
|
16
|
+
shell?: CargoHakariShell;
|
|
17
|
+
}
|
|
18
|
+
export declare function validateCargoCachePolicy(root: string, options?: CargoPolicyOptions): number;
|
|
19
|
+
/**
|
|
20
|
+
* The fix `smoo monorepo update` applies for the policy above: write the
|
|
21
|
+
* nightly resolver configuration, or generate and wire the workspace-hack a
|
|
22
|
+
* stable toolchain needs. Both are idempotent — the second run of either does
|
|
23
|
+
* nothing — because update runs on every repository, not only broken ones.
|
|
24
|
+
*/
|
|
25
|
+
export declare function applyCargoFeatureUnification(root: string, options?: CargoPolicyOptions): void;
|
|
2
26
|
//# sourceMappingURL=cargo-policy.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cargo-policy.d.ts","sourceRoot":"","sources":["../../src/monorepo/cargo-policy.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"cargo-policy.d.ts","sourceRoot":"","sources":["../../src/monorepo/cargo-policy.ts"],"names":[],"mappings":"AAyFA;;;;;GAKG;AACH,MAAM,WAAW,gBAAgB;IAC/B,GAAG,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC;CACrG;AAusBD,MAAM,WAAW,kBAAkB;IACjC,2EAA2E;IAC3E,KAAK,CAAC,EAAE,gBAAgB,CAAC;CAC1B;AAED,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,kBAAuB,GAAG,MAAM,CAwD/F;AAED;;;;;GAKG;AACH,wBAAgB,4BAA4B,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,kBAAuB,GAAG,IAAI,CA8BjG"}
|
|
@@ -2,12 +2,13 @@ import * as _accessExpressionAsString_1 from "typia/lib/internal/_accessExpressi
|
|
|
2
2
|
const __typia_transform__accessExpressionAsString = _accessExpressionAsString_1._accessExpressionAsString;
|
|
3
3
|
import * as _validateReport_1 from "typia/lib/internal/_validateReport";
|
|
4
4
|
import * as _createStandardSchema_1 from "typia/lib/internal/_createStandardSchema";
|
|
5
|
-
import {
|
|
5
|
+
import { spawnSync } from 'node:child_process';
|
|
6
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
6
7
|
import { basename, dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
|
|
7
8
|
import typia from 'typia';
|
|
8
9
|
import { parsePackageJsonText } from '../lib/json.js';
|
|
9
10
|
const validateCargoManifest = (() => {
|
|
10
|
-
const _io0 = input => (undefined === input.workspace || "object" === typeof input.workspace && null !== input.workspace && false === Array.isArray(input.workspace) && _io1(input.workspace)) && (undefined === input["package"] || "object" === typeof input["package"] && null !== input["package"] && false === Array.isArray(input["package"]) && _io2(input["package"])) && (undefined === input.profile || "object" === typeof input.profile && null !== input.profile && false === Array.isArray(input.profile) && _io3(input.profile));
|
|
11
|
+
const _io0 = input => (undefined === input.workspace || "object" === typeof input.workspace && null !== input.workspace && false === Array.isArray(input.workspace) && _io1(input.workspace)) && (undefined === input["package"] || "object" === typeof input["package"] && null !== input["package"] && false === Array.isArray(input["package"]) && _io2(input["package"])) && (undefined === input.profile || "object" === typeof input.profile && null !== input.profile && false === Array.isArray(input.profile) && _io3(input.profile)) && (undefined === input.dependencies || "object" === typeof input.dependencies && null !== input.dependencies && false === Array.isArray(input.dependencies) && _io5(input.dependencies)) && (undefined === input["dev-dependencies"] || "object" === typeof input["dev-dependencies"] && null !== input["dev-dependencies"] && false === Array.isArray(input["dev-dependencies"]) && _io5(input["dev-dependencies"])) && (undefined === input["build-dependencies"] || "object" === typeof input["build-dependencies"] && null !== input["build-dependencies"] && false === Array.isArray(input["build-dependencies"]) && _io5(input["build-dependencies"]));
|
|
11
12
|
const _io1 = input => (undefined === input.members || Array.isArray(input.members) && input.members.every(elem => "string" === typeof elem)) && (undefined === input.exclude || Array.isArray(input.exclude) && input.exclude.every(elem => "string" === typeof elem));
|
|
12
13
|
const _io2 = input => (undefined === input.name || "string" === typeof input.name) && (undefined === input.workspace || "string" === typeof input.workspace || "boolean" === typeof input.workspace);
|
|
13
14
|
const _io3 = input => Object.keys(input).every(key => {
|
|
@@ -17,6 +18,12 @@ const validateCargoManifest = (() => {
|
|
|
17
18
|
return "object" === typeof value && null !== value && false === Array.isArray(value) && _io4(value);
|
|
18
19
|
});
|
|
19
20
|
const _io4 = input => (undefined === input.inherits || "string" === typeof input.inherits) && (undefined === input.incremental || "boolean" === typeof input.incremental) && (undefined === input.debug || "string" === typeof input.debug || "number" === typeof input.debug || "boolean" === typeof input.debug) && (undefined === input["split-debuginfo"] || "string" === typeof input["split-debuginfo"]) && (null !== input["trim-paths"] && (undefined === input["trim-paths"] || "string" === typeof input["trim-paths"] || "boolean" === typeof input["trim-paths"] || Array.isArray(input["trim-paths"]) && input["trim-paths"].every(elem => "string" === typeof elem)));
|
|
21
|
+
const _io5 = input => Object.keys(input).every(key => {
|
|
22
|
+
const value = input[key];
|
|
23
|
+
if (undefined === value)
|
|
24
|
+
return true;
|
|
25
|
+
return true;
|
|
26
|
+
});
|
|
20
27
|
const _vo0 = (input, _path, _exceptionable = true) => [undefined === input.workspace || ("object" === typeof input.workspace && null !== input.workspace && false === Array.isArray(input.workspace) || _report(_exceptionable, {
|
|
21
28
|
path: _path + ".workspace",
|
|
22
29
|
expected: "(undefined | { members?: string[] | undefined; exclude?: string[] | undefined; })",
|
|
@@ -41,6 +48,30 @@ const validateCargoManifest = (() => {
|
|
|
41
48
|
path: _path + ".profile",
|
|
42
49
|
expected: "(Record<string, CargoProfile> | undefined)",
|
|
43
50
|
value: input.profile
|
|
51
|
+
}), undefined === input.dependencies || ("object" === typeof input.dependencies && null !== input.dependencies && false === Array.isArray(input.dependencies) || _report(_exceptionable, {
|
|
52
|
+
path: _path + ".dependencies",
|
|
53
|
+
expected: "(Record<string, unknown> | undefined)",
|
|
54
|
+
value: input.dependencies
|
|
55
|
+
})) && _vo5(input.dependencies, _path + ".dependencies", true && _exceptionable) || _report(_exceptionable, {
|
|
56
|
+
path: _path + ".dependencies",
|
|
57
|
+
expected: "(Record<string, unknown> | undefined)",
|
|
58
|
+
value: input.dependencies
|
|
59
|
+
}), undefined === input["dev-dependencies"] || ("object" === typeof input["dev-dependencies"] && null !== input["dev-dependencies"] && false === Array.isArray(input["dev-dependencies"]) || _report(_exceptionable, {
|
|
60
|
+
path: _path + "[\"dev-dependencies\"]",
|
|
61
|
+
expected: "(Record<string, unknown> | undefined)",
|
|
62
|
+
value: input["dev-dependencies"]
|
|
63
|
+
})) && _vo5(input["dev-dependencies"], _path + "[\"dev-dependencies\"]", true && _exceptionable) || _report(_exceptionable, {
|
|
64
|
+
path: _path + "[\"dev-dependencies\"]",
|
|
65
|
+
expected: "(Record<string, unknown> | undefined)",
|
|
66
|
+
value: input["dev-dependencies"]
|
|
67
|
+
}), undefined === input["build-dependencies"] || ("object" === typeof input["build-dependencies"] && null !== input["build-dependencies"] && false === Array.isArray(input["build-dependencies"]) || _report(_exceptionable, {
|
|
68
|
+
path: _path + "[\"build-dependencies\"]",
|
|
69
|
+
expected: "(Record<string, unknown> | undefined)",
|
|
70
|
+
value: input["build-dependencies"]
|
|
71
|
+
})) && _vo5(input["build-dependencies"], _path + "[\"build-dependencies\"]", true && _exceptionable) || _report(_exceptionable, {
|
|
72
|
+
path: _path + "[\"build-dependencies\"]",
|
|
73
|
+
expected: "(Record<string, unknown> | undefined)",
|
|
74
|
+
value: input["build-dependencies"]
|
|
44
75
|
})].every(flag => flag);
|
|
45
76
|
const _vo1 = (input, _path, _exceptionable = true) => [undefined === input.members || (Array.isArray(input.members) || _report(_exceptionable, {
|
|
46
77
|
path: _path + ".members",
|
|
@@ -123,6 +154,12 @@ const validateCargoManifest = (() => {
|
|
|
123
154
|
expected: "(Array<string> | boolean | string | undefined)",
|
|
124
155
|
value: input["trim-paths"]
|
|
125
156
|
}))].every(flag => flag);
|
|
157
|
+
const _vo5 = (input, _path, _exceptionable = true) => [false === _exceptionable || Object.keys(input).map(key => {
|
|
158
|
+
const value = input[key];
|
|
159
|
+
if (undefined === value)
|
|
160
|
+
return true;
|
|
161
|
+
return true;
|
|
162
|
+
}).every(flag => flag)].every(flag => flag);
|
|
126
163
|
const __is = input => "object" === typeof input && null !== input && false === Array.isArray(input) && _io0(input);
|
|
127
164
|
let errors;
|
|
128
165
|
let _report;
|
|
@@ -175,7 +212,7 @@ const validateCargoConfig = (() => {
|
|
|
175
212
|
expected: "(Array<string> | string | undefined)",
|
|
176
213
|
value: input["rustflags"]
|
|
177
214
|
}));
|
|
178
|
-
const _io0 = input => (undefined === input.build || "object" === typeof input.build && null !== input.build && false === Array.isArray(input.build) && _io1(input.build)) && (undefined === input.target || "object" === typeof input.target && null !== input.target && false === Array.isArray(input.target) && _io2(input.target)) && (undefined === input.env || "object" === typeof input.env && null !== input.env && false === Array.isArray(input.env) && _io4(input.env));
|
|
215
|
+
const _io0 = input => (undefined === input.build || "object" === typeof input.build && null !== input.build && false === Array.isArray(input.build) && _io1(input.build)) && (undefined === input.target || "object" === typeof input.target && null !== input.target && false === Array.isArray(input.target) && _io2(input.target)) && (undefined === input.env || "object" === typeof input.env && null !== input.env && false === Array.isArray(input.env) && _io4(input.env)) && (undefined === input.resolver || "object" === typeof input.resolver && null !== input.resolver && false === Array.isArray(input.resolver) && _io6(input.resolver)) && (undefined === input.unstable || "object" === typeof input.unstable && null !== input.unstable && false === Array.isArray(input.unstable) && _io7(input.unstable));
|
|
179
216
|
const _io1 = input => (undefined === input["target-dir"] || "string" === typeof input["target-dir"]) && _ip0(input) && (undefined === input.incremental || "boolean" === typeof input.incremental);
|
|
180
217
|
const _io2 = input => Object.keys(input).every(key => {
|
|
181
218
|
const value = input[key];
|
|
@@ -191,6 +228,8 @@ const validateCargoConfig = (() => {
|
|
|
191
228
|
return null !== value && undefined !== value && ("string" === typeof value || "object" === typeof value && null !== value && false === Array.isArray(value) && _io5(value));
|
|
192
229
|
});
|
|
193
230
|
const _io5 = input => (undefined === input.value || "string" === typeof input.value) && (undefined === input.relative || "boolean" === typeof input.relative) && (undefined === input.force || "boolean" === typeof input.force);
|
|
231
|
+
const _io6 = input => undefined === input["feature-unification"] || "string" === typeof input["feature-unification"];
|
|
232
|
+
const _io7 = input => undefined === input["feature-unification"] || "boolean" === typeof input["feature-unification"];
|
|
194
233
|
const _vo0 = (input, _path, _exceptionable = true) => [undefined === input.build || ("object" === typeof input.build && null !== input.build && false === Array.isArray(input.build) || _report(_exceptionable, {
|
|
195
234
|
path: _path + ".build",
|
|
196
235
|
expected: _ve0,
|
|
@@ -215,6 +254,22 @@ const validateCargoConfig = (() => {
|
|
|
215
254
|
path: _path + ".env",
|
|
216
255
|
expected: "(Record<string, string | CargoConfigEnvObject> | undefined)",
|
|
217
256
|
value: input.env
|
|
257
|
+
}), undefined === input.resolver || ("object" === typeof input.resolver && null !== input.resolver && false === Array.isArray(input.resolver) || _report(_exceptionable, {
|
|
258
|
+
path: _path + ".resolver",
|
|
259
|
+
expected: "(undefined | { 'feature-unification'?: string | undefined; })",
|
|
260
|
+
value: input.resolver
|
|
261
|
+
})) && _vo6(input.resolver, _path + ".resolver", true && _exceptionable) || _report(_exceptionable, {
|
|
262
|
+
path: _path + ".resolver",
|
|
263
|
+
expected: "(undefined | { 'feature-unification'?: string | undefined; })",
|
|
264
|
+
value: input.resolver
|
|
265
|
+
}), undefined === input.unstable || ("object" === typeof input.unstable && null !== input.unstable && false === Array.isArray(input.unstable) || _report(_exceptionable, {
|
|
266
|
+
path: _path + ".unstable",
|
|
267
|
+
expected: "(undefined | { 'feature-unification'?: boolean | undefined; })",
|
|
268
|
+
value: input.unstable
|
|
269
|
+
})) && _vo7(input.unstable, _path + ".unstable", true && _exceptionable) || _report(_exceptionable, {
|
|
270
|
+
path: _path + ".unstable",
|
|
271
|
+
expected: "(undefined | { 'feature-unification'?: boolean | undefined; })",
|
|
272
|
+
value: input.unstable
|
|
218
273
|
})].every(flag => flag);
|
|
219
274
|
const _vo1 = (input, _path, _exceptionable = true) => [undefined === input["target-dir"] || "string" === typeof input["target-dir"] || _report(_exceptionable, {
|
|
220
275
|
path: _path + "[\"target-dir\"]",
|
|
@@ -279,6 +334,16 @@ const validateCargoConfig = (() => {
|
|
|
279
334
|
expected: "(boolean | undefined)",
|
|
280
335
|
value: input.force
|
|
281
336
|
})].every(flag => flag);
|
|
337
|
+
const _vo6 = (input, _path, _exceptionable = true) => [undefined === input["feature-unification"] || "string" === typeof input["feature-unification"] || _report(_exceptionable, {
|
|
338
|
+
path: _path + "[\"feature-unification\"]",
|
|
339
|
+
expected: "(string | undefined)",
|
|
340
|
+
value: input["feature-unification"]
|
|
341
|
+
})].every(flag => flag);
|
|
342
|
+
const _vo7 = (input, _path, _exceptionable = true) => [undefined === input["feature-unification"] || "boolean" === typeof input["feature-unification"] || _report(_exceptionable, {
|
|
343
|
+
path: _path + "[\"feature-unification\"]",
|
|
344
|
+
expected: "(boolean | undefined)",
|
|
345
|
+
value: input["feature-unification"]
|
|
346
|
+
})].every(flag => flag);
|
|
282
347
|
const __is = input => "object" === typeof input && null !== input && false === Array.isArray(input) && _io0(input);
|
|
283
348
|
let errors;
|
|
284
349
|
let _report;
|
|
@@ -311,6 +376,118 @@ const validateCargoConfig = (() => {
|
|
|
311
376
|
};
|
|
312
377
|
});
|
|
313
378
|
})();
|
|
379
|
+
const validateRustToolchainFile = (() => {
|
|
380
|
+
const _io0 = input => undefined === input.toolchain || "object" === typeof input.toolchain && null !== input.toolchain && false === Array.isArray(input.toolchain) && _io1(input.toolchain);
|
|
381
|
+
const _io1 = input => undefined === input.channel || "string" === typeof input.channel;
|
|
382
|
+
const _vo0 = (input, _path, _exceptionable = true) => [undefined === input.toolchain || ("object" === typeof input.toolchain && null !== input.toolchain && false === Array.isArray(input.toolchain) || _report(_exceptionable, {
|
|
383
|
+
path: _path + ".toolchain",
|
|
384
|
+
expected: "(undefined | { channel?: string | undefined; })",
|
|
385
|
+
value: input.toolchain
|
|
386
|
+
})) && _vo1(input.toolchain, _path + ".toolchain", true && _exceptionable) || _report(_exceptionable, {
|
|
387
|
+
path: _path + ".toolchain",
|
|
388
|
+
expected: "(undefined | { channel?: string | undefined; })",
|
|
389
|
+
value: input.toolchain
|
|
390
|
+
})].every(flag => flag);
|
|
391
|
+
const _vo1 = (input, _path, _exceptionable = true) => [undefined === input.channel || "string" === typeof input.channel || _report(_exceptionable, {
|
|
392
|
+
path: _path + ".channel",
|
|
393
|
+
expected: "(string | undefined)",
|
|
394
|
+
value: input.channel
|
|
395
|
+
})].every(flag => flag);
|
|
396
|
+
const __is = input => "object" === typeof input && null !== input && false === Array.isArray(input) && _io0(input);
|
|
397
|
+
let errors;
|
|
398
|
+
let _report;
|
|
399
|
+
return _createStandardSchema_1._createStandardSchema(input => {
|
|
400
|
+
if (false === __is(input)) {
|
|
401
|
+
errors = [];
|
|
402
|
+
_report = _validateReport_1._validateReport(errors);
|
|
403
|
+
((input, _path, _exceptionable = true) => ("object" === typeof input && null !== input && false === Array.isArray(input) || _report(true, {
|
|
404
|
+
path: _path + "",
|
|
405
|
+
expected: "RustToolchainFile",
|
|
406
|
+
value: input
|
|
407
|
+
})) && _vo0(input, _path + "", true) || _report(true, {
|
|
408
|
+
path: _path + "",
|
|
409
|
+
expected: "RustToolchainFile",
|
|
410
|
+
value: input
|
|
411
|
+
}))(input, "$input", true);
|
|
412
|
+
const success = 0 === errors.length;
|
|
413
|
+
return success ? {
|
|
414
|
+
success,
|
|
415
|
+
data: input
|
|
416
|
+
} : {
|
|
417
|
+
success,
|
|
418
|
+
errors,
|
|
419
|
+
data: input
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
return {
|
|
423
|
+
success: true,
|
|
424
|
+
data: input
|
|
425
|
+
};
|
|
426
|
+
});
|
|
427
|
+
})();
|
|
428
|
+
const validateHakariConfig = (() => {
|
|
429
|
+
const _io0 = input => undefined === input["hakari-package"] || "string" === typeof input["hakari-package"];
|
|
430
|
+
const _vo0 = (input, _path, _exceptionable = true) => [undefined === input["hakari-package"] || "string" === typeof input["hakari-package"] || _report(_exceptionable, {
|
|
431
|
+
path: _path + "[\"hakari-package\"]",
|
|
432
|
+
expected: "(string | undefined)",
|
|
433
|
+
value: input["hakari-package"]
|
|
434
|
+
})].every(flag => flag);
|
|
435
|
+
const __is = input => "object" === typeof input && null !== input && false === Array.isArray(input) && _io0(input);
|
|
436
|
+
let errors;
|
|
437
|
+
let _report;
|
|
438
|
+
return _createStandardSchema_1._createStandardSchema(input => {
|
|
439
|
+
if (false === __is(input)) {
|
|
440
|
+
errors = [];
|
|
441
|
+
_report = _validateReport_1._validateReport(errors);
|
|
442
|
+
((input, _path, _exceptionable = true) => ("object" === typeof input && null !== input && false === Array.isArray(input) || _report(true, {
|
|
443
|
+
path: _path + "",
|
|
444
|
+
expected: "HakariConfig",
|
|
445
|
+
value: input
|
|
446
|
+
})) && _vo0(input, _path + "", true) || _report(true, {
|
|
447
|
+
path: _path + "",
|
|
448
|
+
expected: "HakariConfig",
|
|
449
|
+
value: input
|
|
450
|
+
}))(input, "$input", true);
|
|
451
|
+
const success = 0 === errors.length;
|
|
452
|
+
return success ? {
|
|
453
|
+
success,
|
|
454
|
+
data: input
|
|
455
|
+
} : {
|
|
456
|
+
success,
|
|
457
|
+
errors,
|
|
458
|
+
data: input
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
return {
|
|
462
|
+
success: true,
|
|
463
|
+
data: input
|
|
464
|
+
};
|
|
465
|
+
});
|
|
466
|
+
})();
|
|
467
|
+
const defaultHakariShell = {
|
|
468
|
+
run(directory, args) {
|
|
469
|
+
const result = spawnSync('cargo', ['hakari', ...args], { cwd: directory, encoding: 'utf8' });
|
|
470
|
+
const output = `${result.stdout ?? ''}${result.stderr ?? ''}`.trim();
|
|
471
|
+
// cargo itself exists; a missing subcommand is cargo's own error, not ENOENT.
|
|
472
|
+
const missing = result.error?.code === 'ENOENT' || output.includes('no such command');
|
|
473
|
+
return { code: result.status ?? 1, output, missing };
|
|
474
|
+
},
|
|
475
|
+
};
|
|
476
|
+
/** Cargo's nightly-only workspace feature unification, and the config that turns it on. */
|
|
477
|
+
const RESOLVER_UNIFICATION_BLOCK = [
|
|
478
|
+
'# One feature resolution for the whole workspace. Without it every per-crate',
|
|
479
|
+
'# cargo invocation resolves features for its own selection, so one shared',
|
|
480
|
+
'# dependency is compiled once per selection and no warm target directory is',
|
|
481
|
+
'# reused across crates. Requires the nightly channel the managed devenv',
|
|
482
|
+
'# module pins; on a stable toolchain a cargo-hakari workspace-hack does the',
|
|
483
|
+
'# same job. Both keys are needed: cargo ignores [resolver] without the',
|
|
484
|
+
'# [unstable] opt-in.',
|
|
485
|
+
'[unstable]',
|
|
486
|
+
'feature-unification = true',
|
|
487
|
+
'',
|
|
488
|
+
'[resolver]',
|
|
489
|
+
'feature-unification = "workspace"',
|
|
490
|
+
].join('\n');
|
|
314
491
|
const SKIPPED_DIRECTORY_NAMES = new Set([
|
|
315
492
|
'node_modules',
|
|
316
493
|
'target',
|
|
@@ -488,6 +665,143 @@ function hasAncestorWorkspace(manifest, workspaceRoots) {
|
|
|
488
665
|
return path !== '' && path !== '..' && !path.startsWith(`..${sep}`) && !isAbsolute(path);
|
|
489
666
|
})());
|
|
490
667
|
}
|
|
668
|
+
/**
|
|
669
|
+
* The channel that will actually compile this repository.
|
|
670
|
+
*
|
|
671
|
+
* The managed devenv module wins when it exists: devenv resolves the toolchain
|
|
672
|
+
* through rust-overlay and ignores `rust-toolchain.toml` unless
|
|
673
|
+
* `languages.rust.toolchainFile` names it, so a rust-toolchain file beside that
|
|
674
|
+
* module is decoration. Without the module, rustup's file is the answer. With
|
|
675
|
+
* neither, stable is the safe verdict: it is the channel on which the nightly
|
|
676
|
+
* resolver keys silently do nothing.
|
|
677
|
+
*/
|
|
678
|
+
function isNightlyToolchain(repositoryRoot, workspaceDirectory) {
|
|
679
|
+
const devenvModule = join(repositoryRoot, 'tooling/direnv/devenv.smoo.nix');
|
|
680
|
+
if (existsSync(devenvModule)) {
|
|
681
|
+
const text = readFileSync(devenvModule, 'utf8');
|
|
682
|
+
const rustIndex = text.indexOf('languages.rust');
|
|
683
|
+
const channel = rustIndex === -1 ? null : /channel\s*=\s*"([^"]+)"/.exec(text.slice(rustIndex))?.[1];
|
|
684
|
+
return channel?.startsWith('nightly') === true;
|
|
685
|
+
}
|
|
686
|
+
for (const directory of [workspaceDirectory, repositoryRoot]) {
|
|
687
|
+
const path = join(directory, 'rust-toolchain.toml');
|
|
688
|
+
if (!existsSync(path)) {
|
|
689
|
+
continue;
|
|
690
|
+
}
|
|
691
|
+
try {
|
|
692
|
+
const validation = validateRustToolchainFile(Bun.TOML.parse(readFileSync(path, 'utf8')));
|
|
693
|
+
return validation.success && validation.data.toolchain?.channel?.startsWith('nightly') === true;
|
|
694
|
+
}
|
|
695
|
+
catch {
|
|
696
|
+
return false;
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
return false;
|
|
700
|
+
}
|
|
701
|
+
/**
|
|
702
|
+
* Cargo merges `.cargo/config.toml` from the invocation directory upward, with
|
|
703
|
+
* the deepest file winning. Only the configs at or above the workspace root can
|
|
704
|
+
* govern a command run there, so the effective value is the deepest of those.
|
|
705
|
+
*/
|
|
706
|
+
function effectiveConfigValue(configs, workspaceDirectory, read) {
|
|
707
|
+
let deepest = null;
|
|
708
|
+
for (const loaded of configs) {
|
|
709
|
+
const directory = dirname(dirname(loaded.path));
|
|
710
|
+
if (!pathWithin(directory, workspaceDirectory)) {
|
|
711
|
+
continue;
|
|
712
|
+
}
|
|
713
|
+
const value = read(loaded.config);
|
|
714
|
+
if (value === undefined) {
|
|
715
|
+
continue;
|
|
716
|
+
}
|
|
717
|
+
if (deepest === null || directory.length > deepest.directory.length) {
|
|
718
|
+
deepest = { directory, value };
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
return deepest?.value;
|
|
722
|
+
}
|
|
723
|
+
function workspaceCrates(root, manifests) {
|
|
724
|
+
const members = manifests.filter((manifest) => workspaceContains(root, manifest));
|
|
725
|
+
return root.manifest.package === undefined ? members : [root, ...members];
|
|
726
|
+
}
|
|
727
|
+
const FEATURE_UNIFICATION_FIX = 'Run: smoo monorepo update';
|
|
728
|
+
/**
|
|
729
|
+
* A workspace of two or more crates must resolve features ONCE for all of them.
|
|
730
|
+
*
|
|
731
|
+
* Without that, every per-crate cargo invocation resolves features for its own
|
|
732
|
+
* selection, and one shared dependency is compiled once per selection: the same
|
|
733
|
+
* `nx run cargo-test-<crate>` that should reuse a warm target directory rebuilds
|
|
734
|
+
* its half of the graph instead. Cargo's own mechanism is nightly-only, so a
|
|
735
|
+
* stable toolchain reaches the identical result through a cargo-hakari
|
|
736
|
+
* workspace-hack — a generated crate that depends on the union of features, which
|
|
737
|
+
* every member then depends on.
|
|
738
|
+
*
|
|
739
|
+
* One crate needs neither: there is only one selection to unify.
|
|
740
|
+
*/
|
|
741
|
+
function featureUnificationPolicy(repositoryRoot, workspaceRoots, manifests, configs, shell) {
|
|
742
|
+
let failures = 0;
|
|
743
|
+
for (const root of workspaceRoots) {
|
|
744
|
+
const crates = workspaceCrates(root, manifests);
|
|
745
|
+
if (crates.length < 2) {
|
|
746
|
+
continue;
|
|
747
|
+
}
|
|
748
|
+
const nightly = isNightlyToolchain(repositoryRoot, root.directory);
|
|
749
|
+
const resolver = effectiveConfigValue(configs, root.directory, (config) => config.resolver?.['feature-unification']);
|
|
750
|
+
const unstable = effectiveConfigValue(configs, root.directory, (config) => config.unstable?.['feature-unification']);
|
|
751
|
+
if (nightly && resolver === 'workspace') {
|
|
752
|
+
if (unstable !== true) {
|
|
753
|
+
failures += report(join(root.directory, '.cargo/config.toml'), 'sets [resolver] feature-unification = "workspace" without [unstable] feature-unification = true, ' +
|
|
754
|
+
`so cargo ignores it and every crate still resolves features on its own. ${FEATURE_UNIFICATION_FIX}`);
|
|
755
|
+
}
|
|
756
|
+
continue;
|
|
757
|
+
}
|
|
758
|
+
if (existsSync(join(root.directory, '.config/hakari.toml'))) {
|
|
759
|
+
failures += hakariWorkspaceHackPolicy(root, crates, shell);
|
|
760
|
+
continue;
|
|
761
|
+
}
|
|
762
|
+
failures += report(root.path, `workspace of ${crates.length} crates has no workspace-wide feature unification: ` +
|
|
763
|
+
(nightly
|
|
764
|
+
? 'add [unstable] feature-unification = true and [resolver] feature-unification = "workspace" to .cargo/config.toml'
|
|
765
|
+
: 'the [resolver] feature-unification key needs the nightly channel, so this stable toolchain needs a ' +
|
|
766
|
+
'cargo-hakari workspace-hack (.config/hakari.toml)') +
|
|
767
|
+
`. Every per-crate cargo invocation otherwise recompiles the shared graph for its own feature selection. ${FEATURE_UNIFICATION_FIX}`);
|
|
768
|
+
}
|
|
769
|
+
return failures;
|
|
770
|
+
}
|
|
771
|
+
function hakariWorkspaceHackPolicy(root, crates, shell) {
|
|
772
|
+
const configPath = join(root.directory, '.config/hakari.toml');
|
|
773
|
+
let hackName;
|
|
774
|
+
try {
|
|
775
|
+
const validation = validateHakariConfig(Bun.TOML.parse(readFileSync(configPath, 'utf8')));
|
|
776
|
+
hackName = validation.success ? validation.data['hakari-package'] : undefined;
|
|
777
|
+
}
|
|
778
|
+
catch {
|
|
779
|
+
hackName = undefined;
|
|
780
|
+
}
|
|
781
|
+
if (hackName === undefined || hackName.length === 0) {
|
|
782
|
+
return report(configPath, `must declare hakari-package = "<crate>". ${FEATURE_UNIFICATION_FIX}`);
|
|
783
|
+
}
|
|
784
|
+
const hack = crates.find((crate) => crate.manifest.package?.name === hackName);
|
|
785
|
+
if (hack === undefined) {
|
|
786
|
+
return report(configPath, `names hakari-package "${hackName}", which is not a member of this workspace. ${FEATURE_UNIFICATION_FIX}`);
|
|
787
|
+
}
|
|
788
|
+
const unwired = crates
|
|
789
|
+
.filter((crate) => crate !== hack)
|
|
790
|
+
.filter((crate) => ![crate.manifest.dependencies, crate.manifest['dev-dependencies'], crate.manifest['build-dependencies']].some((table) => table !== undefined && hackName in table))
|
|
791
|
+
.map((crate) => crate.manifest.package?.name ?? crate.path);
|
|
792
|
+
if (unwired.length > 0) {
|
|
793
|
+
return report(root.path, `these crates do not depend on "${hackName}", so cargo-hakari cannot unify their features: ` +
|
|
794
|
+
`${unwired.join(', ')}. ${FEATURE_UNIFICATION_FIX}`);
|
|
795
|
+
}
|
|
796
|
+
const verify = shell.run(root.directory, ['verify']);
|
|
797
|
+
if (verify.missing) {
|
|
798
|
+
return report(configPath, 'needs cargo-hakari, which is not installed; the managed devenv module provides it — reload the shell (direnv reload).');
|
|
799
|
+
}
|
|
800
|
+
if (verify.code !== 0) {
|
|
801
|
+
return report(root.path, `cargo hakari verify rejected the workspace-hack: ${verify.output || 'no output'}. ${FEATURE_UNIFICATION_FIX}`);
|
|
802
|
+
}
|
|
803
|
+
return 0;
|
|
804
|
+
}
|
|
491
805
|
function isWorkspaceMember(manifest, workspaceRoots) {
|
|
492
806
|
if (manifest.manifest.package?.workspace !== undefined) {
|
|
493
807
|
return true;
|
|
@@ -738,7 +1052,7 @@ function reportManifestDirectoryAdvisories(repositoryRoot, ignoredDirectories) {
|
|
|
738
1052
|
console.error(`${advisory.path}:${advisory.line}: env!("CARGO_MANIFEST_DIR") is not path-stable; the patched sccache never normalises env-dep values, so this crate and everything downstream misses on every workspace at a new mount path. Fix it by removing the macro from production code or replacing it with a path-stable value.`);
|
|
739
1053
|
}
|
|
740
1054
|
}
|
|
741
|
-
export function validateCargoCachePolicy(root) {
|
|
1055
|
+
export function validateCargoCachePolicy(root, options = {}) {
|
|
742
1056
|
const repositoryRoot = resolve(root);
|
|
743
1057
|
const discoveredManifestPaths = discoverFiles(repositoryRoot, (name) => name === 'Cargo.toml');
|
|
744
1058
|
const ignoredDirectories = discoverIgnoredSubtrees(discoveredManifestPaths);
|
|
@@ -767,6 +1081,7 @@ export function validateCargoCachePolicy(root) {
|
|
|
767
1081
|
}
|
|
768
1082
|
const workspaceRoots = manifests.filter((loaded) => loaded.manifest.workspace !== undefined);
|
|
769
1083
|
failures += reportManifestPolicy(manifests, workspaceRoots);
|
|
1084
|
+
failures += featureUnificationPolicy(repositoryRoot, workspaceRoots, manifests, configs, options.shell ?? defaultHakariShell);
|
|
770
1085
|
failures += cargoIncrementalPolicy(repositoryRoot, configs, packageJsonPaths, justfilePaths, ignoredDirectories);
|
|
771
1086
|
for (const config of configs) {
|
|
772
1087
|
failures += configPolicy(config, repositoryRoot);
|
|
@@ -774,3 +1089,80 @@ export function validateCargoCachePolicy(root) {
|
|
|
774
1089
|
reportManifestDirectoryAdvisories(repositoryRoot, ignoredDirectories);
|
|
775
1090
|
return failures;
|
|
776
1091
|
}
|
|
1092
|
+
/**
|
|
1093
|
+
* The fix `smoo monorepo update` applies for the policy above: write the
|
|
1094
|
+
* nightly resolver configuration, or generate and wire the workspace-hack a
|
|
1095
|
+
* stable toolchain needs. Both are idempotent — the second run of either does
|
|
1096
|
+
* nothing — because update runs on every repository, not only broken ones.
|
|
1097
|
+
*/
|
|
1098
|
+
export function applyCargoFeatureUnification(root, options = {}) {
|
|
1099
|
+
const repositoryRoot = resolve(root);
|
|
1100
|
+
const shell = options.shell ?? defaultHakariShell;
|
|
1101
|
+
const discoveredManifestPaths = discoverFiles(repositoryRoot, (name) => name === 'Cargo.toml');
|
|
1102
|
+
const manifestPaths = filterIgnoredPaths(discoveredManifestPaths, discoverIgnoredSubtrees(discoveredManifestPaths));
|
|
1103
|
+
const manifests = [];
|
|
1104
|
+
for (const path of manifestPaths) {
|
|
1105
|
+
const manifest = loadManifest(path);
|
|
1106
|
+
if (manifest !== null) {
|
|
1107
|
+
manifests.push({ path, directory: dirname(path), manifest });
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
for (const root of manifests.filter((loaded) => loaded.manifest.workspace !== undefined)) {
|
|
1111
|
+
if (workspaceCrates(root, manifests).length < 2) {
|
|
1112
|
+
continue;
|
|
1113
|
+
}
|
|
1114
|
+
if (isNightlyToolchain(repositoryRoot, root.directory)) {
|
|
1115
|
+
writeResolverUnification(root.directory);
|
|
1116
|
+
continue;
|
|
1117
|
+
}
|
|
1118
|
+
const hakariConfig = join(root.directory, '.config/hakari.toml');
|
|
1119
|
+
const relativeRoot = relative(repositoryRoot, root.directory) || '.';
|
|
1120
|
+
if (!existsSync(hakariConfig)) {
|
|
1121
|
+
console.log(`generating cargo workspace-hack in ${relativeRoot} (cargo hakari init workspace-hack)`);
|
|
1122
|
+
runHakari(shell, root.directory, ['init', 'workspace-hack']);
|
|
1123
|
+
}
|
|
1124
|
+
console.log(`unifying cargo features in ${relativeRoot} (cargo hakari generate, manage-deps)`);
|
|
1125
|
+
runHakari(shell, root.directory, ['generate']);
|
|
1126
|
+
runHakari(shell, root.directory, ['manage-deps', '--yes']);
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
function runHakari(shell, directory, args) {
|
|
1130
|
+
const result = shell.run(directory, args);
|
|
1131
|
+
if (result.missing) {
|
|
1132
|
+
console.error(`cargo hakari is not installed, so the workspace-hack in ${directory} was not updated; the managed devenv module provides it — reload the shell (direnv reload).`);
|
|
1133
|
+
return;
|
|
1134
|
+
}
|
|
1135
|
+
if (result.code !== 0) {
|
|
1136
|
+
console.error(`cargo hakari ${args.join(' ')} failed in ${directory}: ${result.output || 'no output'}`);
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
/**
|
|
1140
|
+
* Append the two keys, never rewrite the file. A `.cargo/config.toml` carries
|
|
1141
|
+
* linkers, env and target settings whose ORDER and comments are load-bearing;
|
|
1142
|
+
* re-emitting parsed TOML would lose both. If either table already exists with
|
|
1143
|
+
* some other content, appending a second one is invalid TOML — so that case is
|
|
1144
|
+
* reported for a human instead of being guessed at.
|
|
1145
|
+
*/
|
|
1146
|
+
function writeResolverUnification(workspaceDirectory) {
|
|
1147
|
+
const path = join(workspaceDirectory, '.cargo/config.toml');
|
|
1148
|
+
const existing = existsSync(path) ? readFileSync(path, 'utf8') : null;
|
|
1149
|
+
if (existing === null) {
|
|
1150
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
1151
|
+
writeFileSync(path, `${RESOLVER_UNIFICATION_BLOCK}\n`);
|
|
1152
|
+
console.log(`writing ${path}`);
|
|
1153
|
+
return;
|
|
1154
|
+
}
|
|
1155
|
+
const parsed = loadConfig(path);
|
|
1156
|
+
if (parsed === null) {
|
|
1157
|
+
return;
|
|
1158
|
+
}
|
|
1159
|
+
if (parsed.resolver?.['feature-unification'] === 'workspace' && parsed.unstable?.['feature-unification'] === true) {
|
|
1160
|
+
return;
|
|
1161
|
+
}
|
|
1162
|
+
if (parsed.resolver !== undefined || parsed.unstable !== undefined) {
|
|
1163
|
+
console.error(`${path}: already declares [resolver] or [unstable]; add feature-unification to the existing tables by hand:\n${RESOLVER_UNIFICATION_BLOCK}`);
|
|
1164
|
+
return;
|
|
1165
|
+
}
|
|
1166
|
+
writeFileSync(path, `${existing.replace(/\n*$/, '\n')}\n${RESOLVER_UNIFICATION_BLOCK}\n`);
|
|
1167
|
+
console.log(`updating ${path}`);
|
|
1168
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/monorepo/index.ts"],"names":[],"mappings":"AAWA,OAAO,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AAC1D,OAAO,EAAE,uBAAuB,EAAE,MAAM,eAAe,CAAC;AA2BxD,MAAM,WAAW,WAAW;IAC1B,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,yBAAyB,CAAC,EAAE,OAAO,CAAC;IACpC,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,4BAA4B;IAC3C,GAAG,CAAC,EAAE,OAAO,CAAC;CACf;AAED,MAAM,WAAW,0BAA0B;IACzC,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,uBAAuB;IACtC,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,qBAAqB;IACpC,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACjE,GAAG,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B;AAED,wBAAsB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAQpF;AAED,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,eAAoB,GAAG,OAAO,CAAC,IAAI,CAAC,CAkBjG;AAED,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/monorepo/index.ts"],"names":[],"mappings":"AAWA,OAAO,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AAC1D,OAAO,EAAE,uBAAuB,EAAE,MAAM,eAAe,CAAC;AA2BxD,MAAM,WAAW,WAAW;IAC1B,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,yBAAyB,CAAC,EAAE,OAAO,CAAC;IACpC,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,4BAA4B;IAC3C,GAAG,CAAC,EAAE,OAAO,CAAC;CACf;AAED,MAAM,WAAW,0BAA0B;IACzC,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,uBAAuB;IACtC,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,qBAAqB;IACpC,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACjE,GAAG,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B;AAED,wBAAsB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAQpF;AAED,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,eAAoB,GAAG,OAAO,CAAC,IAAI,CAAC,CAkBjG;AAED,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAkBpE;AAED,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE;IAAE,IAAI,CAAC,EAAE,OAAO,CAAA;CAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAuBrG;AAED,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAElE;AAED,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,MAAM,GAAG,SAAS,EACxB,OAAO,GAAE,4BAAiC,EAC1C,IAAI,SAAgB,GACnB,IAAI,CAoBN;AAED,wBAAgB,4BAA4B,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,0BAA+B,GAAG,MAAM,CAW3G;AAED,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAI5D;AAED,wBAAsB,gBAAgB,CACpC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,uBAAuB,EAChC,KAAK,GAAE,qBAAoD,GAC1D,OAAO,CAAC,IAAI,CAAC,CA8Bf;AAED,OAAO,EAAE,uBAAuB,EAAE,uBAAuB,EAAE,CAAC"}
|
package/dist/monorepo/index.js
CHANGED
|
@@ -2,7 +2,7 @@ import { appendFileSync, readFileSync, writeFileSync } from 'node:fs';
|
|
|
2
2
|
import { printCommandOutput, run, runResult } from '../lib/run.js';
|
|
3
3
|
import { escapeRegex, getWorkspacePackages, getWorkspacePatterns, listReleasePackages } from '../lib/workspace.js';
|
|
4
4
|
import { readProjectTargets } from '../nx/index.js';
|
|
5
|
-
import { validateCargoCachePolicy } from './cargo-policy.js';
|
|
5
|
+
import { applyCargoFeatureUnification, validateCargoCachePolicy } from './cargo-policy.js';
|
|
6
6
|
import { formatCommitMessage, stagedDeletedPublicPackages, validateBreakingDisclosure, validateCommitMessage, } from './commit-msg.js';
|
|
7
7
|
import { applyWorkspaceGitConfig } from './git-config.js';
|
|
8
8
|
import { syncBunLockfileVersions } from './lockfile.js';
|
|
@@ -49,6 +49,10 @@ export async function updateManagedFiles(root) {
|
|
|
49
49
|
// Tool dependency policy (typescript API 6, @typescript/native for ttsc, nx, …)
|
|
50
50
|
// lives next to managed templates — update must install them, not only rewrite files.
|
|
51
51
|
await applyToolConfigDefaults(root);
|
|
52
|
+
// Rust's half of the same job: a multi-crate Cargo workspace must resolve
|
|
53
|
+
// features once for every member, or each per-crate cargo invocation
|
|
54
|
+
// recompiles the shared graph for its own selection.
|
|
55
|
+
applyCargoFeatureUnification(root);
|
|
52
56
|
syncBunLockfileVersions(root, { mode: 'install' });
|
|
53
57
|
console.log('installing workspace dependencies (bun install)');
|
|
54
58
|
await run('bun', ['install', '--no-summary'], root);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/monorepo/packs/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,cAAc,EAAsB,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/monorepo/packs/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,cAAc,EAAsB,MAAM,mBAAmB,CAAC;AAO5E,OAAO,EAKL,KAAK,sBAAsB,EAS5B,MAAM,sBAAsB,CAAC;AAU9B,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,OAAO,CAAC;IACrB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,CAAC,GAAG,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAClD,WAAW,CAAC,CAAC,GAAG,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IACzD,YAAY,CAAC,CAAC,GAAG,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAC1D,gBAAgB,CAAC,CAAC,GAAG,EAAE,eAAe,GAAG,OAAO,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC;IAClE,iBAAiB,CAAC,CAAC,GAAG,EAAE,eAAe,GAAG,OAAO,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC;CACpE;AAED,MAAM,WAAW,oBAAoB;IACnC,KAAK,CAAC,EAAE,SAAS,YAAY,EAAE,CAAC;IAChC,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,eAAe,EAAE,OAAO,CAAC,EAAE,mBAAmB,KAAK,OAAO,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC;CAC9F;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;CACtB;AA8ID,0DAA0D;AAC1D,eAAO,MAAM,YAAY,gBAAQ,CAAC;AAElC,wBAAsB,YAAY,CAAC,GAAG,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAItE;AAED,wBAAsB,gBAAgB,CACpC,GAAG,EAAE,eAAe,EACpB,OAAO,GAAE,mBAAwB,EACjC,KAAK,GAAE,oBAAyB,GAC/B,OAAO,CAAC,kBAAkB,CAAC,CA0B7B;AAuMD,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,cAAc,EAAE,GAAG,GAAG,CAAC,MAAM,EAAE,sBAAsB,CAAC,CAexG"}
|
|
@@ -2,6 +2,7 @@ import { chmodSync, existsSync, statSync } from 'node:fs';
|
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import { printCommandOutput, runResult, runStatus } from '../../lib/run.js';
|
|
4
4
|
import { readProjectTargets } from '../../nx/index.js';
|
|
5
|
+
import { applyCargoFeatureUnification, validateCargoCachePolicy } from '../cargo-policy.js';
|
|
5
6
|
import { validateGoToolchainAgreement } from '../go-toolchain.js';
|
|
6
7
|
import { syncBunLockfileVersions, validateBunLockfileVersions } from '../lockfile.js';
|
|
7
8
|
import { validateDevenvModuleImport, warnOnManagedFileDrift } from '../managed-files.js';
|
|
@@ -64,6 +65,18 @@ const packs = [
|
|
|
64
65
|
return validateDevenvModuleImport(ctx.root);
|
|
65
66
|
},
|
|
66
67
|
},
|
|
68
|
+
{
|
|
69
|
+
// Cargo's build-cache and feature-unification conventions. `--fix` writes
|
|
70
|
+
// the workspace feature unification a multi-crate workspace needs; the rest
|
|
71
|
+
// of the policy is a verdict a human has to act on.
|
|
72
|
+
name: 'cargo',
|
|
73
|
+
fixPreBuild(ctx) {
|
|
74
|
+
applyCargoFeatureUnification(ctx.root);
|
|
75
|
+
},
|
|
76
|
+
validatePreBuild(ctx) {
|
|
77
|
+
return validateCargoCachePolicy(ctx.root);
|
|
78
|
+
},
|
|
79
|
+
},
|
|
67
80
|
{
|
|
68
81
|
name: 'publishing',
|
|
69
82
|
init(ctx) {
|
|
@@ -221,6 +221,12 @@
|
|
|
221
221
|
# variants per crate and 26 GB of junk before this existed), and a sweep
|
|
222
222
|
# prunes them without touching the warm current-fingerprint surface.
|
|
223
223
|
pkgs.cargo-sweep
|
|
224
|
+
# The stable-toolchain arm of smoo's workspace feature-unification policy:
|
|
225
|
+
# `cargo hakari` generates and wires the workspace-hack crate that unifies
|
|
226
|
+
# features when `[resolver] feature-unification` — nightly-only, and what
|
|
227
|
+
# the channel above selects — is unavailable. `smoo monorepo validate` runs
|
|
228
|
+
# `cargo hakari verify` for a workspace that took that route.
|
|
229
|
+
pkgs.cargo-hakari
|
|
224
230
|
];
|
|
225
231
|
|
|
226
232
|
enterShell = lib.mkMerge [
|