@savvy-web/silk-effects 7.4.0 → 7.5.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.
@@ -1,6 +1,19 @@
1
1
  import { getHeadingText, getVersionBlocks } from "../../utils/version-blocks.js";
2
+ import { toString } from "mdast-util-to-string";
2
3
 
3
4
  //#region src/changesets/remark/plugins/maintenance-note.ts
5
+ /**
6
+ * Placeholder sentence `@changesets/apply-release-plan` (>= 8.1.0) emits as the
7
+ * body of a version block that would otherwise render as a bare heading. Older
8
+ * engine versions left such a block structurally empty.
9
+ */
10
+ const ENGINE_PLACEHOLDER = "No changes in this release.";
11
+ /** True when the block's only content is the engine's empty-release placeholder. */
12
+ function isPlaceholderOnly(tree, block) {
13
+ if (block.endIndex - block.startIndex !== 1) return false;
14
+ const node = tree.children[block.startIndex];
15
+ return node.type === "paragraph" && toString(node).trim() === ENGINE_PLACEHOLDER;
16
+ }
4
17
  function buildNoteChildren(reason) {
5
18
  if (reason.kind === "unspecified" || reason.triggers.length === 0) return [{
6
19
  type: "text",
@@ -31,7 +44,8 @@ const MaintenanceNotePlugin = (options) => {
31
44
  return (tree) => {
32
45
  const block = getVersionBlocks(tree).find((b) => getHeadingText(tree.children[b.headingIndex]) === options.version);
33
46
  if (!block) return;
34
- if (block.endIndex > block.startIndex) return;
47
+ const placeholderOnly = isPlaceholderOnly(tree, block);
48
+ if (block.endIndex > block.startIndex && !placeholderOnly) return;
35
49
  const heading = {
36
50
  type: "heading",
37
51
  depth: 3,
@@ -53,7 +67,7 @@ const MaintenanceNotePlugin = (options) => {
53
67
  }]
54
68
  }]
55
69
  };
56
- tree.children.splice(block.startIndex, 0, heading, list);
70
+ tree.children.splice(block.startIndex, placeholderOnly ? 1 : 0, heading, list);
57
71
  };
58
72
  };
59
73
 
package/index.d.ts CHANGED
@@ -2805,7 +2805,7 @@ declare const ChangesetConfigError_base: new <A extends Record<string, any> = {}
2805
2805
  * @since 0.1.0
2806
2806
  * @public
2807
2807
  */
2808
- declare class ChangesetConfigError extends ChangesetConfigError_base<{
2808
+ export declare class ChangesetConfigError extends ChangesetConfigError_base<{
2809
2809
  readonly path: string;
2810
2810
  readonly reason: string;
2811
2811
  }> {
@@ -2932,7 +2932,7 @@ declare const ChangesetConfigReader_base: Context.ServiceClass<ChangesetConfigRe
2932
2932
  * @since 0.1.0
2933
2933
  * @public
2934
2934
  */
2935
- declare class ChangesetConfigReader extends ChangesetConfigReader_base {
2935
+ export declare class ChangesetConfigReader extends ChangesetConfigReader_base {
2936
2936
  /**
2937
2937
  * Production implementation of {@link ChangesetConfigReader}.
2938
2938
  *
@@ -3680,7 +3680,7 @@ declare const ChangesetConfig_base: Context.ServiceClass<ChangesetConfig, "@savv
3680
3680
  * @since 0.4.0
3681
3681
  * @public
3682
3682
  */
3683
- declare class ChangesetConfig extends ChangesetConfig_base {
3683
+ export declare class ChangesetConfig extends ChangesetConfig_base {
3684
3684
  /**
3685
3685
  * The one ignore matcher: exact name match, or `@scope/*` wildcard.
3686
3686
  *
@@ -6811,7 +6811,7 @@ declare const BiomeSyncError_base: new <A extends Record<string, any> = {}>(args
6811
6811
  * @since 0.1.0
6812
6812
  * @public
6813
6813
  */
6814
- declare class BiomeSyncError extends BiomeSyncError_base<{
6814
+ export declare class BiomeSyncError extends BiomeSyncError_base<{
6815
6815
  readonly path: string;
6816
6816
  readonly reason: string;
6817
6817
  }> {
@@ -6833,7 +6833,7 @@ declare const ConfigNotFoundError_base: new <A extends Record<string, any> = {}>
6833
6833
  * @since 0.1.0
6834
6834
  * @public
6835
6835
  */
6836
- declare class ConfigNotFoundError extends ConfigNotFoundError_base<{
6836
+ export declare class ConfigNotFoundError extends ConfigNotFoundError_base<{
6837
6837
  readonly name: string;
6838
6838
  readonly searchedPaths: ReadonlyArray<string>;
6839
6839
  }> {
@@ -6863,7 +6863,7 @@ declare const PublishTargetBindingError_base: new <A extends Record<string, any>
6863
6863
  * @since 3.1.0
6864
6864
  * @public
6865
6865
  */
6866
- declare class PublishTargetBindingError extends PublishTargetBindingError_base<{
6866
+ export declare class PublishTargetBindingError extends PublishTargetBindingError_base<{
6867
6867
  /** The package whose targets were being resolved. */
6868
6868
  readonly pkg: string;
6869
6869
  /** The directory detection selected, relative to the package root. */
@@ -6889,7 +6889,7 @@ declare const WorkspaceAnalysisError_base: new <A extends Record<string, any> =
6889
6889
  * @since 0.2.0
6890
6890
  * @public
6891
6891
  */
6892
- declare class WorkspaceAnalysisError extends WorkspaceAnalysisError_base<{
6892
+ export declare class WorkspaceAnalysisError extends WorkspaceAnalysisError_base<{
6893
6893
  readonly root: string;
6894
6894
  readonly reason: string;
6895
6895
  }> {
@@ -9842,6 +9842,148 @@ declare const ConfigDiscoveryOptions: Schema.Struct<{
9842
9842
  */
9843
9843
  type ConfigDiscoveryOptions = typeof ConfigDiscoveryOptions.Type;
9844
9844
  //#endregion
9845
+ //#region src/schemas/SavvyInstallSection.d.ts
9846
+ /**
9847
+ * The hooks {@link savvyInstallDeps} can be generated for.
9848
+ *
9849
+ * @remarks
9850
+ * Both are needed, and `post-merge` is the one that matters most in practice:
9851
+ * a fast-forward `git pull` — the batch-alignment case this exists for — fires
9852
+ * `post-merge` and never `post-checkout`. They are separate spellings rather
9853
+ * than one argument-sniffing block because the two hooks disagree on both
9854
+ * halves of the decision: `post-checkout` is handed `<prev> <new> <flag>` and
9855
+ * must ignore a file checkout, while `post-merge` is handed only a squash flag
9856
+ * and has to recover its own range from `ORIG_HEAD`.
9857
+ *
9858
+ * @since 7.5.0
9859
+ * @public
9860
+ */
9861
+ type SavvyInstallHook = "post-checkout" | "post-merge";
9862
+ /**
9863
+ * Section identity for the dependency auto-install block.
9864
+ *
9865
+ * `toolName` is `"savvy-install"`; pair with {@link savvyInstallDeps}.
9866
+ *
9867
+ * @since 7.5.0
9868
+ * @public
9869
+ */
9870
+ export declare const SavvyInstallSection: SectionId;
9871
+ /**
9872
+ * Dependency auto-install shared across Silk Suite hook files.
9873
+ *
9874
+ * @remarks
9875
+ * Brings `node_modules` back in line after a branch switch or a pull, so a batch
9876
+ * of repos pulled to align their dependencies does not each need a manual
9877
+ * install. Installs with lifecycle scripts skipped — `--ignore-scripts`, or
9878
+ * `--mode=skip-build` for Yarn Berry, which dropped the former — because the goal
9879
+ * is to get the dependency tree on disk, not to run a full postinstall.
9880
+ *
9881
+ * **That flag has a cost, and one shape of repo cannot pay it.** Where a package
9882
+ * publishes through a built link directory — `publishConfig.directory` with
9883
+ * `linkDirectory: true`, which is how this monorepo wires `dist/dev/pkg` — the
9884
+ * workspace resolves its own dependencies through a directory that a `prepare`
9885
+ * script has to build, and skipping scripts yields a populated `node_modules`
9886
+ * pointing at nothing.
9887
+ *
9888
+ * Those repos ask for a full install through the LOCAL git config key
9889
+ * `savvy.installLifecycleScripts`, which `savvy init` sets when it finds the
9890
+ * shape. The decision deliberately does NOT read the checked-out tree. Doing so
9891
+ * failed in both directions at once: a branch that merely declared the shape
9892
+ * could turn lifecycle scripts back on just by being checked out, making
9893
+ * `git checkout` of an untrusted revision a code-execution path; and the `jq`
9894
+ * the scan needed is absent on stock macOS and Ubuntu, where the missing answer
9895
+ * silently skipped scripts in precisely the repos that cannot survive it.
9896
+ * `.git/config` is neither checked out nor parsed with `jq`, so it has neither
9897
+ * failure mode.
9898
+ *
9899
+ * The package managers gate dependency scripts themselves on top of this —
9900
+ * pnpm's `strictDepBuilds` with an `allowBuilds` allowlist being the case in
9901
+ * point — but workspace and root lifecycle scripts still run, which is why the
9902
+ * opt-in is local rather than inferred. With the flag on, the hook says on the
9903
+ * way out that scripts were skipped rather than leaving it to be discovered.
9904
+ *
9905
+ * Deliberately self-contained, like `savvyToolchainCheck`: its homes carry
9906
+ * `SavvyHooksSection` but no `SavvyBaseSection`, so it defines its own root, CI
9907
+ * and package-manager lookups rather than depending on `ROOT`, `in_ci` or `PM`.
9908
+ *
9909
+ * Four guards keep it from firing on the many checkouts that are not dependency
9910
+ * events, each a silent no-op:
9911
+ *
9912
+ * - CI, where the runtime action owns installation by construction.
9913
+ * - `SAVVY_SKIP_INSTALL`, the escape hatch for a bisect or a scripted sweep that
9914
+ * does not want an install between steps.
9915
+ * - `post-checkout` only: a branch-flag of `0`, which is `git checkout -- <file>`
9916
+ * and not a move between commits.
9917
+ * - **The gate that makes this affordable:** nothing dependency-related actually
9918
+ * changed across the move. Without it every branch switch would pay for a full
9919
+ * install. A missing `node_modules` overrides it, since there is nothing to be
9920
+ * stale.
9921
+ *
9922
+ * A fresh clone is not among the cases it handles, and cannot be: husky sets
9923
+ * `core.hooksPath` from its own `prepare` script, so until the first manual
9924
+ * install has run there is no hook installed to fire.
9925
+ *
9926
+ * The install's exit status is swallowed and its output goes to stderr. Git
9927
+ * ignores what `post-checkout` and `post-merge` return, so a failure here must
9928
+ * not look like a failed checkout; the hint names the escape hatch instead.
9929
+ *
9930
+ * Every probe that can legitimately fail — no `ORIG_HEAD` to compare against, an
9931
+ * unresolvable range — is neutralised with `|| true` rather than left to its own
9932
+ * status. These blocks are co-owned and consumer content can sit below them, so
9933
+ * under a hook running `set -e` a bare failing substitution would abort the whole
9934
+ * file and take those later sections with it.
9935
+ *
9936
+ * @param hook - Which hook the block is being generated for; decides the
9937
+ * argument guard and how the comparison range is recovered.
9938
+ * @returns The install shell, with no surrounding markers or trailing newline.
9939
+ *
9940
+ * @since 7.5.0
9941
+ * @public
9942
+ */
9943
+ export declare function savvyInstallDeps(hook: SavvyInstallHook): string;
9944
+ /**
9945
+ * The local git config key that authorizes lifecycle scripts during a hook install.
9946
+ *
9947
+ * @remarks
9948
+ * Local scope only. It lives in `.git/config`, which is never checked out, so no
9949
+ * incoming revision can set it — that is the whole point of reading the decision
9950
+ * from here rather than from a manifest in the tree.
9951
+ *
9952
+ * @since 7.5.0
9953
+ * @public
9954
+ */
9955
+ export declare const LIFECYCLE_SCRIPTS_CONFIG_KEY = "savvy.installLifecycleScripts";
9956
+ /**
9957
+ * Whether `manifest` publishes through a built link directory.
9958
+ *
9959
+ * @remarks
9960
+ * `publishConfig.directory` with `linkDirectory: true` means consumers of this
9961
+ * package resolve it through a directory that a `prepare` script has to produce,
9962
+ * so an install that skips lifecycle scripts leaves the link pointing at nothing.
9963
+ * A workspace containing any such package is one whose owner probably wants
9964
+ * {@link LIFECYCLE_SCRIPTS_CONFIG_KEY} set.
9965
+ *
9966
+ * Reporting the shape is deliberately separate from acting on it: this answers
9967
+ * "does this repo need scripts", and a human still decides whether hook-time
9968
+ * installs may run them.
9969
+ *
9970
+ * @param manifest - A parsed `package.json`; any non-object reads as `false`.
9971
+ *
9972
+ * @since 7.5.0
9973
+ * @public
9974
+ */
9975
+ export declare function publishesBuiltLinkDirectory(manifest: unknown): boolean;
9976
+ /**
9977
+ * Build the dependency auto-install block for `hook`.
9978
+ *
9979
+ * @param hook - Which hook the section is destined for.
9980
+ * @returns A shell `Section` (`commentStyle: hash`) keyed `SAVVY-INSTALL`.
9981
+ *
9982
+ * @since 7.5.0
9983
+ * @public
9984
+ */
9985
+ export declare function savvyInstallBlock(hook: SavvyInstallHook): Section;
9986
+ //#endregion
9845
9987
  //#region src/schemas/SavvySections.d.ts
9846
9988
  /**
9847
9989
  * Section identity for the shared package-manager preamble.
@@ -9856,7 +9998,7 @@ type ConfigDiscoveryOptions = typeof ConfigDiscoveryOptions.Type;
9856
9998
  * @since 0.5.0
9857
9999
  * @public
9858
10000
  */
9859
- declare const SavvyBaseSection: SectionId;
10001
+ export declare const SavvyBaseSection: SectionId;
9860
10002
  /**
9861
10003
  * Section identity for the shared repo-hygiene block.
9862
10004
  *
@@ -9865,7 +10007,7 @@ declare const SavvyBaseSection: SectionId;
9865
10007
  * @since 0.5.0
9866
10008
  * @public
9867
10009
  */
9868
- declare const SavvyHooksSection: SectionId;
10010
+ export declare const SavvyHooksSection: SectionId;
9869
10011
  /**
9870
10012
  * Package-manager detection preamble shared across Silk Suite hook files.
9871
10013
  *
@@ -9880,7 +10022,7 @@ declare const SavvyHooksSection: SectionId;
9880
10022
  * @since 0.5.0
9881
10023
  * @public
9882
10024
  */
9883
- declare function savvyBasePreamble(): string;
10025
+ export declare function savvyBasePreamble(): string;
9884
10026
  /**
9885
10027
  * Repo-hygiene block shared across Silk Suite hook files.
9886
10028
  *
@@ -9893,7 +10035,7 @@ declare function savvyBasePreamble(): string;
9893
10035
  * @since 0.5.0
9894
10036
  * @public
9895
10037
  */
9896
- declare function savvyHooksHygiene(): string;
10038
+ export declare function savvyHooksHygiene(): string;
9897
10039
  /**
9898
10040
  * Build a consumer's one-line tool section so every consumer calls the shared base
9899
10041
  * helpers identically.
@@ -9922,7 +10064,7 @@ declare function savvyHooksHygiene(): string;
9922
10064
  * @since 0.5.0
9923
10065
  * @public
9924
10066
  */
9925
- declare function savvyToolSection(toolName: string, command: string): Section;
10067
+ export declare function savvyToolSection(toolName: string, command: string): Section;
9926
10068
  /**
9927
10069
  * Section identity for the package-manager toolchain drift check.
9928
10070
  *
@@ -9931,7 +10073,7 @@ declare function savvyToolSection(toolName: string, command: string): Section;
9931
10073
  * @since 7.3.0
9932
10074
  * @public
9933
10075
  */
9934
- declare const SavvyToolchainSection: SectionId;
10076
+ export declare const SavvyToolchainSection: SectionId;
9935
10077
  /**
9936
10078
  * Package-manager drift check shared across Silk Suite hook files.
9937
10079
  *
@@ -9959,7 +10101,7 @@ declare const SavvyToolchainSection: SectionId;
9959
10101
  * @since 7.3.0
9960
10102
  * @public
9961
10103
  */
9962
- declare function savvyToolchainCheck(): string;
10104
+ export declare function savvyToolchainCheck(): string;
9963
10105
  //#endregion
9964
10106
  //#region src/schemas/WorkspaceAnalysisSchemas.d.ts
9965
10107
  declare const SilkPublishConfig_base: Schema.Class<SilkPublishConfig, Schema.Struct<{
@@ -9989,7 +10131,7 @@ declare const SilkPublishConfig_base: Schema.Class<SilkPublishConfig, Schema.Str
9989
10131
  * @since 0.2.0
9990
10132
  * @public
9991
10133
  */
9992
- declare class SilkPublishConfig extends SilkPublishConfig_base {}
10134
+ export declare class SilkPublishConfig extends SilkPublishConfig_base {}
9993
10135
  declare const AnalyzedWorkspace_base: Schema.Class<AnalyzedWorkspace, Schema.TaggedStruct<"AnalyzedWorkspace", {
9994
10136
  readonly name: Schema.String;
9995
10137
  readonly version: Schema.Struct<{
@@ -10013,7 +10155,7 @@ declare const AnalyzedWorkspace_base: Schema.Class<AnalyzedWorkspace, Schema.Tag
10013
10155
  * @since 0.2.0
10014
10156
  * @public
10015
10157
  */
10016
- declare class AnalyzedWorkspace extends AnalyzedWorkspace_base {
10158
+ export declare class AnalyzedWorkspace extends AnalyzedWorkspace_base {
10017
10159
  get isRoot(): boolean;
10018
10160
  get isPublishable(): boolean;
10019
10161
  get isReleasable(): boolean;
@@ -10095,7 +10237,7 @@ declare const WorkspaceAnalysis_base: Schema.Class<WorkspaceAnalysis, Schema.Tag
10095
10237
  * @since 0.2.0
10096
10238
  * @public
10097
10239
  */
10098
- declare class WorkspaceAnalysis extends WorkspaceAnalysis_base {
10240
+ export declare class WorkspaceAnalysis extends WorkspaceAnalysis_base {
10099
10241
  findWorkspace(name: string): Option.Option<AnalyzedWorkspace>;
10100
10242
  get rootWorkspace(): Option.Option<AnalyzedWorkspace>;
10101
10243
  get publishableWorkspaces(): ReadonlyArray<AnalyzedWorkspace>;
@@ -10121,7 +10263,7 @@ declare class WorkspaceAnalysis extends WorkspaceAnalysis_base {
10121
10263
  * @since 0.1.0
10122
10264
  * @public
10123
10265
  */
10124
- declare function extractSemver(version: string): string;
10266
+ export declare function extractSemver(version: string): string;
10125
10267
  /**
10126
10268
  * Build the expected Biome JSON schema URL for a given version.
10127
10269
  *
@@ -10131,7 +10273,7 @@ declare function extractSemver(version: string): string;
10131
10273
  * @since 0.1.0
10132
10274
  * @public
10133
10275
  */
10134
- declare function buildSchemaUrl(version: string): string;
10276
+ export declare function buildSchemaUrl(version: string): string;
10135
10277
  /**
10136
10278
  * The {@link BiomeSchemaSync} service shape.
10137
10279
  *
@@ -10192,7 +10334,7 @@ declare const BiomeSchemaSync_base: Context.ServiceClass<BiomeSchemaSync, "@savv
10192
10334
  * @since 0.1.0
10193
10335
  * @public
10194
10336
  */
10195
- declare class BiomeSchemaSync extends BiomeSchemaSync_base {
10337
+ export declare class BiomeSchemaSync extends BiomeSchemaSync_base {
10196
10338
  /**
10197
10339
  * Production implementation of {@link BiomeSchemaSync}.
10198
10340
  *
@@ -10268,7 +10410,7 @@ declare const ConfigDiscovery_base: Context.ServiceClass<ConfigDiscovery, "@savv
10268
10410
  * @since 0.1.0
10269
10411
  * @public
10270
10412
  */
10271
- declare class ConfigDiscovery extends ConfigDiscovery_base {
10413
+ export declare class ConfigDiscovery extends ConfigDiscovery_base {
10272
10414
  /**
10273
10415
  * Production implementation of {@link ConfigDiscovery}.
10274
10416
  *
@@ -10396,7 +10538,7 @@ interface PublishablePackage {
10396
10538
  * @since 0.4.0
10397
10539
  * @public
10398
10540
  */
10399
- declare class SilkPublishability {
10541
+ export declare class SilkPublishability {
10400
10542
  /**
10401
10543
  * Apply silk publishability rules to a raw `package.json` and the bundler's resolved
10402
10544
  * target binding. Targets-first precedence:
@@ -10479,7 +10621,7 @@ declare class SilkPublishability {
10479
10621
  * @since 1.0.0
10480
10622
  * @public
10481
10623
  */
10482
- declare const readTargetsBinding: (fs: FileSystem.FileSystem, pkgPath: string) => Effect.Effect<TargetsBinding | null>;
10624
+ export declare const readTargetsBinding: (fs: FileSystem.FileSystem, pkgPath: string) => Effect.Effect<TargetsBinding | null>;
10483
10625
  //#endregion
10484
10626
  //#region src/services/SilkWorkspaceAnalyzer.d.ts
10485
10627
  /**
@@ -10530,7 +10672,7 @@ declare const SilkWorkspaceAnalyzer_base: Context.ServiceClass<SilkWorkspaceAnal
10530
10672
  * @since 0.2.0
10531
10673
  * @public
10532
10674
  */
10533
- declare class SilkWorkspaceAnalyzer extends SilkWorkspaceAnalyzer_base {
10675
+ export declare class SilkWorkspaceAnalyzer extends SilkWorkspaceAnalyzer_base {
10534
10676
  /**
10535
10677
  * Production implementation of {@link SilkWorkspaceAnalyzer}.
10536
10678
  *
@@ -10900,5 +11042,5 @@ declare namespace index_d_exports$5 {
10900
11042
  export { AffectedResult, AffectedResultType, CacheDiagnosis, CacheDiagnosisType, DryRunParseError, GlobalHashSummary, GraphNode, MissExplanation, NotATurboRepoError, PackageCacheStatus, TaskGraphResult, TaskGraphResultType, TurboCache, TurboDigest, TurboDryRun, TurboDryRunType, TurboDryTask, TurboDryTaskType, TurboEnvVars, TurboError, TurboExecError, TurboGlobalCacheInputs, TurboInspector, TurboInspectorShape, TurboNotInstalledError };
10901
11043
  }
10902
11044
  //#endregion
10903
- export { AnalyzedWorkspace, BiomeSchemaSync, type BiomeSchemaSyncShape, BiomeSyncError, type BiomeSyncOptions, type BiomeSyncResult, ChangesetConfig, ChangesetConfigError, type ChangesetConfigFile, ChangesetConfigReader, type ChangesetConfigReaderShape, type ChangesetConfigShape, type ChangesetMode, index_d_exports as Changesets, index_d_exports$1 as Commitlint, type CommitlintPlugin, type CommitlintUserConfig, ConfigDiscovery, type ConfigDiscoveryOptions, type ConfigDiscoveryShape, type ConfigLocation, ConfigNotFoundError, type ConfigSource, index_d_exports$2 as Lint, index_d_exports$3 as PrBody, type PromptConfig, type PromptSettings, PublishTargetBindingError, type PublishablePackage, type RawPackageJson, type RawPublishConfig, type RawPublishTargets, type RawTargetObject, type RawTargetValue, index_d_exports$4 as Repos, type RuleApplicability, type RuleConfigTuple, type RuleSeverity, type RulesConfig, SavvyBaseSection, SavvyHooksSection, SavvyToolchainSection, type SilkChangesetConfigFile, SilkPublishConfig, SilkPublishability, SilkWorkspaceAnalyzer, type SilkWorkspaceAnalyzerShape, type TargetBinding, type TargetGroupBinding, type TargetsBinding, index_d_exports$5 as Turbo, WorkspaceAnalysis, WorkspaceAnalysisError, buildSchemaUrl, extractSemver, readTargetsBinding, savvyBasePreamble, savvyHooksHygiene, savvyToolSection, savvyToolchainCheck };
11045
+ export { type BiomeSchemaSyncShape, type BiomeSyncOptions, type BiomeSyncResult, type ChangesetConfigFile, type ChangesetConfigReaderShape, type ChangesetConfigShape, type ChangesetMode, index_d_exports as Changesets, index_d_exports$1 as Commitlint, type CommitlintPlugin, type CommitlintUserConfig, type ConfigDiscoveryOptions, type ConfigDiscoveryShape, type ConfigLocation, type ConfigSource, index_d_exports$2 as Lint, index_d_exports$3 as PrBody, type PromptConfig, type PromptSettings, type PublishablePackage, type RawPackageJson, type RawPublishConfig, type RawPublishTargets, type RawTargetObject, type RawTargetValue, index_d_exports$4 as Repos, type RuleApplicability, type RuleConfigTuple, type RuleSeverity, type RulesConfig, type SavvyInstallHook, type SilkChangesetConfigFile, type SilkWorkspaceAnalyzerShape, type TargetBinding, type TargetGroupBinding, type TargetsBinding, index_d_exports$5 as Turbo };
10904
11046
  //# sourceMappingURL=index.d.ts.map
package/index.js CHANGED
@@ -12,10 +12,11 @@ import { WorkspaceAnalysisError } from "./errors/WorkspaceAnalysisError.js";
12
12
  import { lint_exports } from "./lint/index.js";
13
13
  import { pr_body_exports } from "./pr-body/index.js";
14
14
  import { repos_exports } from "./repos/index.js";
15
+ import { LIFECYCLE_SCRIPTS_CONFIG_KEY, SavvyInstallSection, publishesBuiltLinkDirectory, savvyInstallBlock, savvyInstallDeps } from "./schemas/SavvyInstallSection.js";
15
16
  import { AnalyzedWorkspace, SilkPublishConfig, WorkspaceAnalysis } from "./schemas/WorkspaceAnalysisSchemas.js";
16
17
  import { BiomeSchemaSync, buildSchemaUrl, extractSemver } from "./services/BiomeSchemaSync.js";
17
18
  import { ConfigDiscovery } from "./services/ConfigDiscovery.js";
18
19
  import { SilkWorkspaceAnalyzer } from "./services/SilkWorkspaceAnalyzer.js";
19
20
  import { turbo_exports } from "./turbo/index.js";
20
21
 
21
- export { AnalyzedWorkspace, BiomeSchemaSync, BiomeSyncError, ChangesetConfig, ChangesetConfigError, ChangesetConfigReader, changesets_exports as Changesets, commitlint_exports as Commitlint, ConfigDiscovery, ConfigNotFoundError, lint_exports as Lint, pr_body_exports as PrBody, PublishTargetBindingError, repos_exports as Repos, SavvyBaseSection, SavvyHooksSection, SavvyToolchainSection, SilkPublishConfig, SilkPublishability, SilkWorkspaceAnalyzer, turbo_exports as Turbo, WorkspaceAnalysis, WorkspaceAnalysisError, buildSchemaUrl, extractSemver, readTargetsBinding, savvyBasePreamble, savvyHooksHygiene, savvyToolSection, savvyToolchainCheck };
22
+ export { AnalyzedWorkspace, BiomeSchemaSync, BiomeSyncError, ChangesetConfig, ChangesetConfigError, ChangesetConfigReader, changesets_exports as Changesets, commitlint_exports as Commitlint, ConfigDiscovery, ConfigNotFoundError, LIFECYCLE_SCRIPTS_CONFIG_KEY, lint_exports as Lint, pr_body_exports as PrBody, PublishTargetBindingError, repos_exports as Repos, SavvyBaseSection, SavvyHooksSection, SavvyInstallSection, SavvyToolchainSection, SilkPublishConfig, SilkPublishability, SilkWorkspaceAnalyzer, turbo_exports as Turbo, WorkspaceAnalysis, WorkspaceAnalysisError, buildSchemaUrl, extractSemver, publishesBuiltLinkDirectory, readTargetsBinding, savvyBasePreamble, savvyHooksHygiene, savvyInstallBlock, savvyInstallDeps, savvyToolSection, savvyToolchainCheck };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@savvy-web/silk-effects",
3
- "version": "7.4.0",
3
+ "version": "7.5.0",
4
4
  "private": false,
5
5
  "description": "Shared Effect library for Silk Suite conventions",
6
6
  "homepage": "https://github.com/savvy-web/systems/tree/main/packages/silk-effects",
@@ -29,19 +29,19 @@
29
29
  "./package.json": "./package.json"
30
30
  },
31
31
  "dependencies": {
32
- "@changesets/apply-release-plan": "^8.0.0",
32
+ "@changesets/apply-release-plan": "^8.1.0",
33
33
  "@changesets/changelog-git": "^1.0.0",
34
34
  "@changesets/config": "^4.0.0",
35
- "@changesets/get-github-info": "^1.0.0",
36
- "@changesets/get-release-plan": "^5.0.0",
37
- "@effected/github-references": "^0.1.0",
38
- "@effected/glob": "^0.4.0",
39
- "@effected/jsonc": "^0.8.1",
40
- "@effected/markdown": "^0.8.1",
41
- "@effected/package-json": "^0.13.0",
42
- "@effected/templates": "^0.4.0",
43
- "@effected/walker": "^0.5.0",
44
- "@effected/yaml": "^0.12.0",
35
+ "@changesets/get-github-info": "^1.0.1",
36
+ "@changesets/get-release-plan": "^5.0.1",
37
+ "@effected/github-references": "^0.2.0",
38
+ "@effected/glob": "^0.5.0",
39
+ "@effected/jsonc": "^0.9.0",
40
+ "@effected/markdown": "^0.9.0",
41
+ "@effected/package-json": "^0.14.0",
42
+ "@effected/templates": "^0.5.0",
43
+ "@effected/walker": "^0.6.0",
44
+ "@effected/yaml": "^0.13.0",
45
45
  "@manypkg/get-packages": "^3.1.0",
46
46
  "mdast-util-heading-range": "^4.0.0",
47
47
  "mdast-util-to-string": "^4.0.0",
@@ -54,9 +54,9 @@
54
54
  "unist-util-visit": "^5.1.0"
55
55
  },
56
56
  "peerDependencies": {
57
- "@effected/commands": "^0.5.0",
58
- "@effected/git": "^0.10.0",
59
- "@effected/workspaces": "^0.19.0",
60
- "effect": "4.0.0-rc.109"
57
+ "@effected/commands": "^0.6.0",
58
+ "@effected/git": "^0.11.0",
59
+ "@effected/workspaces": "^0.20.0",
60
+ "effect": "4.0.0-rc.112"
61
61
  }
62
62
  }
@@ -0,0 +1,232 @@
1
+ import { CommentStyle, SectionId } from "@effected/templates";
2
+
3
+ //#region src/schemas/SavvyInstallSection.ts
4
+ /**
5
+ * Section identity for the dependency auto-install block.
6
+ *
7
+ * `toolName` is `"savvy-install"`; pair with {@link savvyInstallDeps}.
8
+ *
9
+ * @since 7.5.0
10
+ * @public
11
+ */
12
+ const SavvyInstallSection = SectionId.make({
13
+ key: "SAVVY-INSTALL",
14
+ commentStyle: CommentStyle.hash
15
+ });
16
+ /**
17
+ * Pathspecs whose change across a checkout or merge means `node_modules` is stale.
18
+ *
19
+ * @remarks
20
+ * Git's default pathspec globbing does not stop a wildcard at a slash, so the
21
+ * one nested-manifest entry below covers every workspace manifest at any depth
22
+ * without needing a recursive spelling. Manifests are listed alongside lockfiles
23
+ * because a dependency edit that has not been installed yet moves the manifest
24
+ * first and the lockfile not at all.
25
+ */
26
+ const DEPENDENCY_PATHS = [
27
+ "package.json",
28
+ "*/package.json",
29
+ "pnpm-lock.yaml",
30
+ "pnpm-workspace.yaml",
31
+ "package-lock.json",
32
+ "npm-shrinkwrap.json",
33
+ "yarn.lock",
34
+ "bun.lock",
35
+ "bun.lockb"
36
+ ].map((path) => `'${path}'`).join(" ");
37
+ /**
38
+ * Dependency auto-install shared across Silk Suite hook files.
39
+ *
40
+ * @remarks
41
+ * Brings `node_modules` back in line after a branch switch or a pull, so a batch
42
+ * of repos pulled to align their dependencies does not each need a manual
43
+ * install. Installs with lifecycle scripts skipped — `--ignore-scripts`, or
44
+ * `--mode=skip-build` for Yarn Berry, which dropped the former — because the goal
45
+ * is to get the dependency tree on disk, not to run a full postinstall.
46
+ *
47
+ * **That flag has a cost, and one shape of repo cannot pay it.** Where a package
48
+ * publishes through a built link directory — `publishConfig.directory` with
49
+ * `linkDirectory: true`, which is how this monorepo wires `dist/dev/pkg` — the
50
+ * workspace resolves its own dependencies through a directory that a `prepare`
51
+ * script has to build, and skipping scripts yields a populated `node_modules`
52
+ * pointing at nothing.
53
+ *
54
+ * Those repos ask for a full install through the LOCAL git config key
55
+ * `savvy.installLifecycleScripts`, which `savvy init` sets when it finds the
56
+ * shape. The decision deliberately does NOT read the checked-out tree. Doing so
57
+ * failed in both directions at once: a branch that merely declared the shape
58
+ * could turn lifecycle scripts back on just by being checked out, making
59
+ * `git checkout` of an untrusted revision a code-execution path; and the `jq`
60
+ * the scan needed is absent on stock macOS and Ubuntu, where the missing answer
61
+ * silently skipped scripts in precisely the repos that cannot survive it.
62
+ * `.git/config` is neither checked out nor parsed with `jq`, so it has neither
63
+ * failure mode.
64
+ *
65
+ * The package managers gate dependency scripts themselves on top of this —
66
+ * pnpm's `strictDepBuilds` with an `allowBuilds` allowlist being the case in
67
+ * point — but workspace and root lifecycle scripts still run, which is why the
68
+ * opt-in is local rather than inferred. With the flag on, the hook says on the
69
+ * way out that scripts were skipped rather than leaving it to be discovered.
70
+ *
71
+ * Deliberately self-contained, like `savvyToolchainCheck`: its homes carry
72
+ * `SavvyHooksSection` but no `SavvyBaseSection`, so it defines its own root, CI
73
+ * and package-manager lookups rather than depending on `ROOT`, `in_ci` or `PM`.
74
+ *
75
+ * Four guards keep it from firing on the many checkouts that are not dependency
76
+ * events, each a silent no-op:
77
+ *
78
+ * - CI, where the runtime action owns installation by construction.
79
+ * - `SAVVY_SKIP_INSTALL`, the escape hatch for a bisect or a scripted sweep that
80
+ * does not want an install between steps.
81
+ * - `post-checkout` only: a branch-flag of `0`, which is `git checkout -- <file>`
82
+ * and not a move between commits.
83
+ * - **The gate that makes this affordable:** nothing dependency-related actually
84
+ * changed across the move. Without it every branch switch would pay for a full
85
+ * install. A missing `node_modules` overrides it, since there is nothing to be
86
+ * stale.
87
+ *
88
+ * A fresh clone is not among the cases it handles, and cannot be: husky sets
89
+ * `core.hooksPath` from its own `prepare` script, so until the first manual
90
+ * install has run there is no hook installed to fire.
91
+ *
92
+ * The install's exit status is swallowed and its output goes to stderr. Git
93
+ * ignores what `post-checkout` and `post-merge` return, so a failure here must
94
+ * not look like a failed checkout; the hint names the escape hatch instead.
95
+ *
96
+ * Every probe that can legitimately fail — no `ORIG_HEAD` to compare against, an
97
+ * unresolvable range — is neutralised with `|| true` rather than left to its own
98
+ * status. These blocks are co-owned and consumer content can sit below them, so
99
+ * under a hook running `set -e` a bare failing substitution would abort the whole
100
+ * file and take those later sections with it.
101
+ *
102
+ * @param hook - Which hook the block is being generated for; decides the
103
+ * argument guard and how the comparison range is recovered.
104
+ * @returns The install shell, with no surrounding markers or trailing newline.
105
+ *
106
+ * @since 7.5.0
107
+ * @public
108
+ */
109
+ function savvyInstallDeps(hook) {
110
+ const skip = `! { [ -n "$CI" ] || [ -n "$GITHUB_ACTIONS" ] || [ -n "$SAVVY_SKIP_INSTALL" ]; }`;
111
+ return `if ${hook === "post-checkout" ? `${skip} && [ "$3" = "1" ]` : skip}; then
112
+ install_root=$(git rev-parse --show-toplevel 2>/dev/null) || true
113
+ ${hook === "post-checkout" ? ` install_from="$1"
114
+ install_to="$2"` : ` install_from=$(git rev-parse --verify --quiet ORIG_HEAD 2>/dev/null) || true
115
+ install_to="HEAD"`}
116
+ install_pm=""
117
+ if [ -n "$install_root" ] && [ -f "$install_root/package.json" ]; then
118
+ if command -v jq >/dev/null 2>&1; then
119
+ install_pm=$(jq -r '.packageManager // empty' "$install_root/package.json" 2>/dev/null | cut -d'@' -f1)
120
+ fi
121
+ if [ -z "$install_pm" ]; then
122
+ if [ -f "$install_root/pnpm-lock.yaml" ]; then install_pm="pnpm"
123
+ elif [ -f "$install_root/yarn.lock" ]; then install_pm="yarn"
124
+ elif [ -f "$install_root/bun.lock" ] || [ -f "$install_root/bun.lockb" ]; then install_pm="bun"
125
+ else install_pm="npm"; fi
126
+ fi
127
+ fi
128
+ # packageManager comes from the checked-out revision, so it names an executable
129
+ # the tree controls. 'command -v' proves a binary exists, not that it is a
130
+ # package manager, so the name is checked against the supported four first.
131
+ case "$install_pm" in
132
+ npm|pnpm|yarn|bun) ;;
133
+ *) install_pm="" ;;
134
+ esac
135
+ # Nothing to bring up to date unless a manifest or lockfile actually moved.
136
+ # A missing node_modules skips the diff outright: there is no tree to be stale.
137
+ install_stale=""
138
+ if [ -n "$install_pm" ]; then
139
+ if [ ! -d "$install_root/node_modules" ]; then
140
+ install_stale=1
141
+ elif [ -n "$install_from" ] && [ -n "$install_to" ]; then
142
+ install_stale=$(git diff --name-only "$install_from" "$install_to" -- ${DEPENDENCY_PATHS} 2>/dev/null | head -n 1) || true
143
+ fi
144
+ fi
145
+ if [ -n "$install_stale" ] && command -v "$install_pm" >/dev/null 2>&1; then
146
+ # Whether lifecycle scripts run is a LOCAL decision, read from .git/config,
147
+ # which no checkout can rewrite. Default off. 'savvy init' turns it on for a
148
+ # workspace that publishes through built link directories, where the links
149
+ # point at directories a prepare script has to produce.
150
+ install_scripts=$(git -C "$install_root" config --bool --get savvy.installLifecycleScripts 2>/dev/null) || true
151
+ if [ "$install_scripts" = "true" ]; then
152
+ install_flag=""
153
+ else
154
+ # Berry dropped --ignore-scripts for the install mode; Classic never knew the
155
+ # mode. An unreadable major reads as Berry, the likelier of the two today.
156
+ install_flag="--ignore-scripts"
157
+ if [ "$install_pm" = "yarn" ]; then
158
+ case "$(yarn --version 2>/dev/null | cut -d. -f1)" in
159
+ 1) ;;
160
+ *) install_flag="--mode=skip-build" ;;
161
+ esac
162
+ fi
163
+ fi
164
+ # ':+' so a full install announces itself as "pnpm install", not "pnpm install ".
165
+ printf '↻ dependencies changed, running %s install%s\\n' "$install_pm" "\${install_flag:+ $install_flag}" >&2
166
+ # Unquoted on purpose: empty must expand to NO argument rather than an empty
167
+ # one. Safe because every value is a static flag literal from this generator.
168
+ if ( cd "$install_root" && "$install_pm" install $install_flag ) >&2; then
169
+ if [ -n "$install_flag" ]; then
170
+ printf ' Lifecycle scripts were skipped. Run a full install if you need build outputs.\\n' >&2
171
+ fi
172
+ else
173
+ printf '⚠ %s install failed; run it yourself to see why.\\n' "$install_pm" >&2
174
+ printf ' Set SAVVY_SKIP_INSTALL=1 to stop this hook from trying.\\n' >&2
175
+ fi
176
+ fi
177
+ unset install_root install_from install_to install_pm install_stale install_scripts install_flag
178
+ fi`;
179
+ }
180
+ /**
181
+ * The local git config key that authorizes lifecycle scripts during a hook install.
182
+ *
183
+ * @remarks
184
+ * Local scope only. It lives in `.git/config`, which is never checked out, so no
185
+ * incoming revision can set it — that is the whole point of reading the decision
186
+ * from here rather than from a manifest in the tree.
187
+ *
188
+ * @since 7.5.0
189
+ * @public
190
+ */
191
+ const LIFECYCLE_SCRIPTS_CONFIG_KEY = "savvy.installLifecycleScripts";
192
+ /**
193
+ * Whether `manifest` publishes through a built link directory.
194
+ *
195
+ * @remarks
196
+ * `publishConfig.directory` with `linkDirectory: true` means consumers of this
197
+ * package resolve it through a directory that a `prepare` script has to produce,
198
+ * so an install that skips lifecycle scripts leaves the link pointing at nothing.
199
+ * A workspace containing any such package is one whose owner probably wants
200
+ * {@link LIFECYCLE_SCRIPTS_CONFIG_KEY} set.
201
+ *
202
+ * Reporting the shape is deliberately separate from acting on it: this answers
203
+ * "does this repo need scripts", and a human still decides whether hook-time
204
+ * installs may run them.
205
+ *
206
+ * @param manifest - A parsed `package.json`; any non-object reads as `false`.
207
+ *
208
+ * @since 7.5.0
209
+ * @public
210
+ */
211
+ function publishesBuiltLinkDirectory(manifest) {
212
+ if (typeof manifest !== "object" || manifest === null) return false;
213
+ const { publishConfig } = manifest;
214
+ if (typeof publishConfig !== "object" || publishConfig === null) return false;
215
+ const { directory, linkDirectory } = publishConfig;
216
+ return typeof directory === "string" && directory.length > 0 && linkDirectory === true;
217
+ }
218
+ /**
219
+ * Build the dependency auto-install block for `hook`.
220
+ *
221
+ * @param hook - Which hook the section is destined for.
222
+ * @returns A shell `Section` (`commentStyle: hash`) keyed `SAVVY-INSTALL`.
223
+ *
224
+ * @since 7.5.0
225
+ * @public
226
+ */
227
+ function savvyInstallBlock(hook) {
228
+ return SavvyInstallSection.section(savvyInstallDeps(hook));
229
+ }
230
+
231
+ //#endregion
232
+ export { LIFECYCLE_SCRIPTS_CONFIG_KEY, SavvyInstallSection, publishesBuiltLinkDirectory, savvyInstallBlock, savvyInstallDeps };