@smoothbricks/cli 0.5.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/monorepo/git-config.d.ts.map +1 -1
- package/dist/monorepo/git-config.js +7 -0
- package/dist/monorepo/managed-files.d.ts +15 -0
- package/dist/monorepo/managed-files.d.ts.map +1 -1
- package/dist/monorepo/managed-files.js +31 -2
- package/dist/monorepo/packed-package.js +8 -6
- package/managed/raw/gitattributes +8 -0
- package/managed/raw/tooling/direnv/merge-newer-pins.sh +48 -0
- package/package.json +1 -1
- package/src/monorepo/git-config.ts +10 -0
- package/src/monorepo/managed-files.test.ts +28 -0
- package/src/monorepo/managed-files.ts +33 -2
- package/src/monorepo/merge-newer-pins.test.ts +59 -0
- package/src/monorepo/packed-package.ts +8 -6
- package/src/pr/index.test.ts +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"git-config.d.ts","sourceRoot":"","sources":["../../src/monorepo/git-config.ts"],"names":[],"mappings":"AAKA,wBAAsB,uBAAuB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"git-config.d.ts","sourceRoot":"","sources":["../../src/monorepo/git-config.ts"],"names":[],"mappings":"AAKA,wBAAsB,uBAAuB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAsBzE"}
|
|
@@ -10,6 +10,13 @@ export async function applyWorkspaceGitConfig(root) {
|
|
|
10
10
|
const gitDir = resolve(root, decode(gitDirResult.stdout).trim());
|
|
11
11
|
const tooling = join(root, 'tooling');
|
|
12
12
|
await $ `git config --local include.path ${join(tooling, 'workspace.gitconfig')}`.cwd(root);
|
|
13
|
+
// Keep the newer runtime version pins on any merge (nvfetcher overlay +
|
|
14
|
+
// devenv.lock) so a mirror sync's `git am --3way` never stalls on a version
|
|
15
|
+
// conflict. Mapped by the managed .gitattributes (merge=smoo-newer-pins);
|
|
16
|
+
// implemented in tooling/direnv/merge-newer-pins.sh. The next `devenv shell`
|
|
17
|
+
// regenerates package.json fields from the resulting runtime.
|
|
18
|
+
await $ `git config --local merge.smoo-newer-pins.name ${'keep the newer devenv/nvfetcher runtime pins'}`.cwd(root);
|
|
19
|
+
await $ `git config --local merge.smoo-newer-pins.driver ${'bash tooling/direnv/merge-newer-pins.sh %O %A %B %P'}`.cwd(root);
|
|
13
20
|
linkHook(gitDir, tooling, 'pre-commit');
|
|
14
21
|
linkHook(gitDir, tooling, 'commit-msg');
|
|
15
22
|
}
|
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Repos may append their own content to a managed file below this marker —
|
|
3
|
+
* e.g. extra merge drivers in .gitattributes. Everything from the marker
|
|
4
|
+
* line onward is preserved verbatim across updates and ignored by the
|
|
5
|
+
* drift check; the managed section above it stays byte-exact.
|
|
6
|
+
*/
|
|
7
|
+
export declare const LOCAL_SECTION_MARKER = "# smoo-local: everything below this line is repo-owned and preserved";
|
|
8
|
+
/** Split a managed target's content into the managed part and the repo-owned tail. */
|
|
9
|
+
declare function splitLocalSection(current: string): {
|
|
10
|
+
managed: string;
|
|
11
|
+
localTail: string;
|
|
12
|
+
};
|
|
13
|
+
/** Test seam for the pure splitter. */
|
|
14
|
+
export declare const splitLocalSectionForTest: typeof splitLocalSection;
|
|
1
15
|
export interface FileResult {
|
|
2
16
|
target: string;
|
|
3
17
|
action: 'created' | 'updated' | 'unchanged' | 'skipped' | 'skipped-symlink' | 'drifted' | 'ok-symlink';
|
|
@@ -5,4 +19,5 @@ export interface FileResult {
|
|
|
5
19
|
export declare function applyManagedFiles(root: string, mode: 'update' | 'check' | 'diff'): FileResult[];
|
|
6
20
|
export declare function printResults(results: FileResult[]): void;
|
|
7
21
|
export declare function validateManagedFiles(root: string): number;
|
|
22
|
+
export {};
|
|
8
23
|
//# sourceMappingURL=managed-files.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"managed-files.d.ts","sourceRoot":"","sources":["../../src/monorepo/managed-files.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"managed-files.d.ts","sourceRoot":"","sources":["../../src/monorepo/managed-files.ts"],"names":[],"mappings":"AAUA;;;;;GAKG;AACH,eAAO,MAAM,oBAAoB,yEAAyE,CAAC;AAU3G,sFAAsF;AACtF,iBAAS,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAIlF;AAED,uCAAuC;AACvC,eAAO,MAAM,wBAAwB,0BAAoB,CAAC;AAE1D,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,SAAS,GAAG,SAAS,GAAG,WAAW,GAAG,SAAS,GAAG,iBAAiB,GAAG,SAAS,GAAG,YAAY,CAAC;CACxG;AA8GD,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,GAAG,OAAO,GAAG,MAAM,GAAG,UAAU,EAAE,CAG/F;AA8KD,wBAAgB,YAAY,CAAC,OAAO,EAAE,UAAU,EAAE,GAAG,IAAI,CAIxD;AAED,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAQzD"}
|
|
@@ -5,6 +5,22 @@ import { fileURLToPath } from 'node:url';
|
|
|
5
5
|
import { listReleasePackages, readPackageJson } from '../lib/workspace.js';
|
|
6
6
|
import { renderCiWorkflowYaml } from './ci-workflow.js';
|
|
7
7
|
import { renderPublishWorkflowYaml } from './publish-workflow.js';
|
|
8
|
+
/**
|
|
9
|
+
* Repos may append their own content to a managed file below this marker —
|
|
10
|
+
* e.g. extra merge drivers in .gitattributes. Everything from the marker
|
|
11
|
+
* line onward is preserved verbatim across updates and ignored by the
|
|
12
|
+
* drift check; the managed section above it stays byte-exact.
|
|
13
|
+
*/
|
|
14
|
+
export const LOCAL_SECTION_MARKER = '# smoo-local: everything below this line is repo-owned and preserved';
|
|
15
|
+
/** Split a managed target's content into the managed part and the repo-owned tail. */
|
|
16
|
+
function splitLocalSection(current) {
|
|
17
|
+
const index = current.indexOf(LOCAL_SECTION_MARKER);
|
|
18
|
+
if (index === -1)
|
|
19
|
+
return { managed: current, localTail: '' };
|
|
20
|
+
return { managed: current.slice(0, index), localTail: current.slice(index) };
|
|
21
|
+
}
|
|
22
|
+
/** Test seam for the pure splitter. */
|
|
23
|
+
export const splitLocalSectionForTest = splitLocalSection;
|
|
8
24
|
const managedFiles = [
|
|
9
25
|
{
|
|
10
26
|
kind: 'raw',
|
|
@@ -46,6 +62,17 @@ const managedFiles = [
|
|
|
46
62
|
source: 'git-format-staged.yml',
|
|
47
63
|
target: '.git-format-staged.yml',
|
|
48
64
|
},
|
|
65
|
+
{
|
|
66
|
+
kind: 'raw',
|
|
67
|
+
source: 'gitattributes',
|
|
68
|
+
target: '.gitattributes',
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
kind: 'raw',
|
|
72
|
+
source: 'tooling/direnv/merge-newer-pins.sh',
|
|
73
|
+
target: 'tooling/direnv/merge-newer-pins.sh',
|
|
74
|
+
executable: true,
|
|
75
|
+
},
|
|
49
76
|
{
|
|
50
77
|
kind: 'generated',
|
|
51
78
|
source: 'ci-workflow',
|
|
@@ -103,13 +130,15 @@ function applyManagedFile(root, file, mode, context) {
|
|
|
103
130
|
throw new Error(`${file.target} exists but is not a regular file or symlink`);
|
|
104
131
|
}
|
|
105
132
|
const current = readFileSync(target, 'utf8');
|
|
106
|
-
|
|
133
|
+
const { managed, localTail } = splitLocalSection(current);
|
|
134
|
+
if (managed === content || (localTail !== '' && managed === `${content}\n`)) {
|
|
107
135
|
return { target: file.target, action: 'unchanged' };
|
|
108
136
|
}
|
|
109
137
|
if (mode === 'check' || mode === 'diff') {
|
|
110
138
|
return { target: file.target, action: 'drifted' };
|
|
111
139
|
}
|
|
112
|
-
|
|
140
|
+
const next = localTail === '' ? content : `${content}\n${localTail}`;
|
|
141
|
+
writeManagedFile(target, next, file.executable === true);
|
|
113
142
|
return { target: file.target, action: 'updated' };
|
|
114
143
|
}
|
|
115
144
|
if (mode === 'check' || mode === 'diff') {
|
|
@@ -69,7 +69,7 @@ async function validatePackedManifest(root, pkg, packed) {
|
|
|
69
69
|
async function validateAttw(root, pkg, packed) {
|
|
70
70
|
const attw = await loadAttwCore();
|
|
71
71
|
const analysis = await attw.checkPackage(await createAttwPackageFromTarball(attw, root, packed.path), {
|
|
72
|
-
excludeEntrypoints:
|
|
72
|
+
excludeEntrypoints: nonJsExportEntrypoints(pkg.json.exports),
|
|
73
73
|
});
|
|
74
74
|
if (!isRecord(analysis) || analysis.types === false) {
|
|
75
75
|
return 0;
|
|
@@ -205,19 +205,21 @@ function formatResolutionKind(kind) {
|
|
|
205
205
|
}
|
|
206
206
|
return kind;
|
|
207
207
|
}
|
|
208
|
-
function
|
|
208
|
+
function nonJsExportEntrypoints(exports) {
|
|
209
209
|
if (!isRecord(exports)) {
|
|
210
210
|
return [];
|
|
211
211
|
}
|
|
212
212
|
return Object.entries(exports)
|
|
213
|
-
.filter(([key, value]) => key.startsWith('.') &&
|
|
213
|
+
.filter(([key, value]) => key.startsWith('.') && exportPointsToNonJs(value))
|
|
214
214
|
.map(([key]) => key);
|
|
215
215
|
}
|
|
216
|
-
function
|
|
216
|
+
function exportPointsToNonJs(value) {
|
|
217
217
|
if (typeof value === 'string') {
|
|
218
|
-
|
|
218
|
+
// attw resolves every entrypoint as a module; assets can never have types.
|
|
219
|
+
// Existence of the target files is still validated by publint.
|
|
220
|
+
return value.endsWith('.wasm') || value.endsWith('.css');
|
|
219
221
|
}
|
|
220
|
-
return isRecord(value) && Object.values(value).some(
|
|
222
|
+
return isRecord(value) && Object.values(value).some(exportPointsToNonJs);
|
|
221
223
|
}
|
|
222
224
|
async function packPackage(root, pkg) {
|
|
223
225
|
const packageDir = join(root, pkg.path);
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# smoo-managed. Keep the newer runtime version pins on any merge (especially a
|
|
2
|
+
# mirror sync's `git am --3way`) instead of stalling on a conflict. The driver
|
|
3
|
+
# `smoo-newer-pins` is defined by `smoo` (git config, installed at devenv setup)
|
|
4
|
+
# and implemented in tooling/direnv/merge-newer-pins.sh. Files not present in a
|
|
5
|
+
# given repo are simply ignored.
|
|
6
|
+
tooling/direnv/nixpkgs-overlay/_sources/generated.json merge=smoo-newer-pins
|
|
7
|
+
tooling/direnv/nixpkgs-overlay/_sources/generated.nix merge=smoo-newer-pins
|
|
8
|
+
tooling/direnv/devenv.lock merge=smoo-newer-pins
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# smoo-managed git merge driver: keep whichever side pins the NEWER version.
|
|
3
|
+
#
|
|
4
|
+
# Runtime version pins (nvfetcher overlay _sources/generated.{json,nix}) and the
|
|
5
|
+
# devenv/flake lock (devenv.lock) frequently differ across branches or across a
|
|
6
|
+
# mirror sync's `git am --3way`. Instead of stalling on a conflict, keep the
|
|
7
|
+
# higher pin wholesale so the tree always converges to the newest runtime; the
|
|
8
|
+
# next `devenv shell` then regenerates package.json engine/packageManager from
|
|
9
|
+
# it (see monorepo/runtime.ts syncRootRuntimeVersions).
|
|
10
|
+
#
|
|
11
|
+
# Wired by `smoo` (managed .gitattributes -> merge=smoo-newer-pins, and
|
|
12
|
+
# `git config merge.smoo-newer-pins.driver` installed by applyWorkspaceGitConfig).
|
|
13
|
+
# git invokes it as: merge-newer-pins.sh %O %A %B %P
|
|
14
|
+
# %O base, %A ours (result written here), %B theirs, %P pathname.
|
|
15
|
+
set -euo pipefail
|
|
16
|
+
|
|
17
|
+
ours="$2"
|
|
18
|
+
theirs="$3"
|
|
19
|
+
|
|
20
|
+
# Flake locks are ordered by lastModified (unix seconds); nvfetcher outputs by
|
|
21
|
+
# the semver in their version fields. A given file is one kind or the other.
|
|
22
|
+
max_last_modified() {
|
|
23
|
+
grep -oE '"lastModified"[[:space:]]*:[[:space:]]*[0-9]+' "$1" 2>/dev/null \
|
|
24
|
+
| grep -oE '[0-9]+' | sort -n | tail -1
|
|
25
|
+
}
|
|
26
|
+
max_semver() {
|
|
27
|
+
grep -oE 'version[[:space:]":=]*[0-9]+\.[0-9]+\.[0-9]+' "$1" 2>/dev/null \
|
|
28
|
+
| grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | sort -V | tail -1
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
ours_lm="$(max_last_modified "$ours" || true)"
|
|
32
|
+
theirs_lm="$(max_last_modified "$theirs" || true)"
|
|
33
|
+
|
|
34
|
+
if [ -n "${ours_lm}${theirs_lm}" ]; then
|
|
35
|
+
# Flake lock: numeric lastModified comparison.
|
|
36
|
+
if [ -n "$theirs_lm" ] && [ "${theirs_lm:-0}" -gt "${ours_lm:-0}" ]; then
|
|
37
|
+
cp "$theirs" "$ours"
|
|
38
|
+
fi
|
|
39
|
+
else
|
|
40
|
+
# nvfetcher / version-pinned file: semver comparison.
|
|
41
|
+
ov="$(max_semver "$ours" || true)"
|
|
42
|
+
tv="$(max_semver "$theirs" || true)"
|
|
43
|
+
newest="$(printf '%s\n%s\n' "${ov:-0.0.0}" "${tv:-0.0.0}" | sort -V | tail -1)"
|
|
44
|
+
if [ -n "$tv" ] && [ "$newest" = "$tv" ] && [ "$tv" != "${ov:-}" ]; then
|
|
45
|
+
cp "$theirs" "$ours"
|
|
46
|
+
fi
|
|
47
|
+
fi
|
|
48
|
+
exit 0
|
package/package.json
CHANGED
|
@@ -13,6 +13,16 @@ export async function applyWorkspaceGitConfig(root: string): Promise<void> {
|
|
|
13
13
|
const tooling = join(root, 'tooling');
|
|
14
14
|
|
|
15
15
|
await $`git config --local include.path ${join(tooling, 'workspace.gitconfig')}`.cwd(root);
|
|
16
|
+
|
|
17
|
+
// Keep the newer runtime version pins on any merge (nvfetcher overlay +
|
|
18
|
+
// devenv.lock) so a mirror sync's `git am --3way` never stalls on a version
|
|
19
|
+
// conflict. Mapped by the managed .gitattributes (merge=smoo-newer-pins);
|
|
20
|
+
// implemented in tooling/direnv/merge-newer-pins.sh. The next `devenv shell`
|
|
21
|
+
// regenerates package.json fields from the resulting runtime.
|
|
22
|
+
await $`git config --local merge.smoo-newer-pins.name ${'keep the newer devenv/nvfetcher runtime pins'}`.cwd(root);
|
|
23
|
+
await $`git config --local merge.smoo-newer-pins.driver ${'bash tooling/direnv/merge-newer-pins.sh %O %A %B %P'}`.cwd(
|
|
24
|
+
root,
|
|
25
|
+
);
|
|
16
26
|
linkHook(gitDir, tooling, 'pre-commit');
|
|
17
27
|
linkHook(gitDir, tooling, 'commit-msg');
|
|
18
28
|
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { describe, expect, it } from 'bun:test';
|
|
2
|
+
import { LOCAL_SECTION_MARKER, splitLocalSectionForTest } from './managed-files.js';
|
|
3
|
+
|
|
4
|
+
const MANAGED = '# managed content\npath merge=driver\n';
|
|
5
|
+
|
|
6
|
+
describe('managed-file local sections', () => {
|
|
7
|
+
it('content without a marker is entirely managed', () => {
|
|
8
|
+
const { managed, localTail } = splitLocalSectionForTest(MANAGED);
|
|
9
|
+
expect(managed).toBe(MANAGED);
|
|
10
|
+
expect(localTail).toBe('');
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it('everything from the marker onward is the repo-owned tail', () => {
|
|
14
|
+
const tail = `${LOCAL_SECTION_MARKER}\ncustom/*.jsonl merge=custom-log\n`;
|
|
15
|
+
const { managed, localTail } = splitLocalSectionForTest(`${MANAGED}\n${tail}`);
|
|
16
|
+
expect(managed).toBe(`${MANAGED}\n`);
|
|
17
|
+
expect(localTail).toBe(tail);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('a tail directly after the managed content tolerates the separating newline', () => {
|
|
21
|
+
// The compare rule accepts `managed === content + '\n'` when a tail exists,
|
|
22
|
+
// so update → check round-trips as unchanged.
|
|
23
|
+
const written = `${MANAGED}\n${LOCAL_SECTION_MARKER}\nextra\n`;
|
|
24
|
+
const { managed, localTail } = splitLocalSectionForTest(written);
|
|
25
|
+
expect(managed).toBe(`${MANAGED}\n`);
|
|
26
|
+
expect(localTail.startsWith(LOCAL_SECTION_MARKER)).toBe(true);
|
|
27
|
+
});
|
|
28
|
+
});
|
|
@@ -8,6 +8,14 @@ import { renderPublishWorkflowYaml } from './publish-workflow.js';
|
|
|
8
8
|
|
|
9
9
|
type ManagedKind = 'raw' | 'template' | 'generated';
|
|
10
10
|
|
|
11
|
+
/**
|
|
12
|
+
* Repos may append their own content to a managed file below this marker —
|
|
13
|
+
* e.g. extra merge drivers in .gitattributes. Everything from the marker
|
|
14
|
+
* line onward is preserved verbatim across updates and ignored by the
|
|
15
|
+
* drift check; the managed section above it stays byte-exact.
|
|
16
|
+
*/
|
|
17
|
+
export const LOCAL_SECTION_MARKER = '# smoo-local: everything below this line is repo-owned and preserved';
|
|
18
|
+
|
|
11
19
|
interface ManagedFile {
|
|
12
20
|
kind: ManagedKind;
|
|
13
21
|
source: string;
|
|
@@ -16,6 +24,16 @@ interface ManagedFile {
|
|
|
16
24
|
releasePackagesOnly?: boolean;
|
|
17
25
|
}
|
|
18
26
|
|
|
27
|
+
/** Split a managed target's content into the managed part and the repo-owned tail. */
|
|
28
|
+
function splitLocalSection(current: string): { managed: string; localTail: string } {
|
|
29
|
+
const index = current.indexOf(LOCAL_SECTION_MARKER);
|
|
30
|
+
if (index === -1) return { managed: current, localTail: '' };
|
|
31
|
+
return { managed: current.slice(0, index), localTail: current.slice(index) };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Test seam for the pure splitter. */
|
|
35
|
+
export const splitLocalSectionForTest = splitLocalSection;
|
|
36
|
+
|
|
19
37
|
export interface FileResult {
|
|
20
38
|
target: string;
|
|
21
39
|
action: 'created' | 'updated' | 'unchanged' | 'skipped' | 'skipped-symlink' | 'drifted' | 'ok-symlink';
|
|
@@ -78,6 +96,17 @@ const managedFiles: ManagedFile[] = [
|
|
|
78
96
|
source: 'git-format-staged.yml',
|
|
79
97
|
target: '.git-format-staged.yml',
|
|
80
98
|
},
|
|
99
|
+
{
|
|
100
|
+
kind: 'raw',
|
|
101
|
+
source: 'gitattributes',
|
|
102
|
+
target: '.gitattributes',
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
kind: 'raw',
|
|
106
|
+
source: 'tooling/direnv/merge-newer-pins.sh',
|
|
107
|
+
target: 'tooling/direnv/merge-newer-pins.sh',
|
|
108
|
+
executable: true,
|
|
109
|
+
},
|
|
81
110
|
{
|
|
82
111
|
kind: 'generated',
|
|
83
112
|
source: 'ci-workflow',
|
|
@@ -143,13 +172,15 @@ function applyManagedFile(
|
|
|
143
172
|
throw new Error(`${file.target} exists but is not a regular file or symlink`);
|
|
144
173
|
}
|
|
145
174
|
const current = readFileSync(target, 'utf8');
|
|
146
|
-
|
|
175
|
+
const { managed, localTail } = splitLocalSection(current);
|
|
176
|
+
if (managed === content || (localTail !== '' && managed === `${content}\n`)) {
|
|
147
177
|
return { target: file.target, action: 'unchanged' };
|
|
148
178
|
}
|
|
149
179
|
if (mode === 'check' || mode === 'diff') {
|
|
150
180
|
return { target: file.target, action: 'drifted' };
|
|
151
181
|
}
|
|
152
|
-
|
|
182
|
+
const next = localTail === '' ? content : `${content}\n${localTail}`;
|
|
183
|
+
writeManagedFile(target, next, file.executable === true);
|
|
153
184
|
return { target: file.target, action: 'updated' };
|
|
154
185
|
}
|
|
155
186
|
if (mode === 'check' || mode === 'diff') {
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { describe, expect, it } from 'bun:test';
|
|
2
|
+
import { spawnSync } from 'node:child_process';
|
|
3
|
+
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
5
|
+
import { dirname, join, resolve } from 'node:path';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
7
|
+
|
|
8
|
+
const script = resolve(
|
|
9
|
+
dirname(fileURLToPath(import.meta.url)),
|
|
10
|
+
'..',
|
|
11
|
+
'..',
|
|
12
|
+
'managed/raw/tooling/direnv/merge-newer-pins.sh',
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
// Invoke the merge driver as git does: `driver %O %A %B %P`, result written to %A (ours).
|
|
16
|
+
function mergedOurs(oursContent: string, theirsContent: string): string {
|
|
17
|
+
const dir = mkdtempSync(join(tmpdir(), 'mnp-'));
|
|
18
|
+
try {
|
|
19
|
+
const base = join(dir, 'base');
|
|
20
|
+
const ours = join(dir, 'ours');
|
|
21
|
+
const theirs = join(dir, 'theirs');
|
|
22
|
+
writeFileSync(base, '');
|
|
23
|
+
writeFileSync(ours, oursContent);
|
|
24
|
+
writeFileSync(theirs, theirsContent);
|
|
25
|
+
const r = spawnSync('bash', [script, base, ours, theirs, 'pins'], { encoding: 'utf8' });
|
|
26
|
+
expect(r.status).toBe(0);
|
|
27
|
+
return readFileSync(ours, 'utf8');
|
|
28
|
+
} finally {
|
|
29
|
+
rmSync(dir, { recursive: true, force: true });
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
describe('smoo-newer-pins merge driver', () => {
|
|
34
|
+
it('nvfetcher JSON: takes theirs when it pins a newer semver', () => {
|
|
35
|
+
expect(mergedOurs('{"bun":{"version": "1.3.13"}}', '{"bun":{"version": "1.3.14"}}')).toContain('1.3.14');
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('nvfetcher JSON: keeps ours when ours pins the newer semver', () => {
|
|
39
|
+
expect(mergedOurs('{"bun":{"version": "1.3.14"}}', '{"bun":{"version": "1.3.13"}}')).toContain('1.3.14');
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('nvfetcher nix form: takes the newer semver', () => {
|
|
43
|
+
expect(mergedOurs('version = "1.3.13";', 'version = "1.3.14";')).toContain('1.3.14');
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('flake lock: takes theirs when lastModified is newer', () => {
|
|
47
|
+
const out = mergedOurs('{"nixpkgs":{"lastModified": 1777573317}}', '{"nixpkgs":{"lastModified": 1779717400}}');
|
|
48
|
+
expect(out).toContain('1779717400');
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('flake lock: keeps ours when ours lastModified is newer', () => {
|
|
52
|
+
const out = mergedOurs('{"nixpkgs":{"lastModified": 1779717400}}', '{"nixpkgs":{"lastModified": 1777573317}}');
|
|
53
|
+
expect(out).toContain('1779717400');
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('equal versions: keeps ours unchanged (idempotent)', () => {
|
|
57
|
+
expect(mergedOurs('{"v": "1.3.14", "note": "ours"}', '{"v": "1.3.14", "note": "theirs"}')).toContain('ours');
|
|
58
|
+
});
|
|
59
|
+
});
|
|
@@ -90,7 +90,7 @@ async function validatePackedManifest(
|
|
|
90
90
|
async function validateAttw(root: string, pkg: PackageInfo, packed: { path: string }): Promise<number> {
|
|
91
91
|
const attw = await loadAttwCore();
|
|
92
92
|
const analysis = await attw.checkPackage(await createAttwPackageFromTarball(attw, root, packed.path), {
|
|
93
|
-
excludeEntrypoints:
|
|
93
|
+
excludeEntrypoints: nonJsExportEntrypoints(pkg.json.exports),
|
|
94
94
|
});
|
|
95
95
|
if (!isRecord(analysis) || analysis.types === false) {
|
|
96
96
|
return 0;
|
|
@@ -242,20 +242,22 @@ function formatResolutionKind(kind: string): string {
|
|
|
242
242
|
return kind;
|
|
243
243
|
}
|
|
244
244
|
|
|
245
|
-
function
|
|
245
|
+
function nonJsExportEntrypoints(exports: unknown): string[] {
|
|
246
246
|
if (!isRecord(exports)) {
|
|
247
247
|
return [];
|
|
248
248
|
}
|
|
249
249
|
return Object.entries(exports)
|
|
250
|
-
.filter(([key, value]) => key.startsWith('.') &&
|
|
250
|
+
.filter(([key, value]) => key.startsWith('.') && exportPointsToNonJs(value))
|
|
251
251
|
.map(([key]) => key);
|
|
252
252
|
}
|
|
253
253
|
|
|
254
|
-
function
|
|
254
|
+
function exportPointsToNonJs(value: unknown): boolean {
|
|
255
255
|
if (typeof value === 'string') {
|
|
256
|
-
|
|
256
|
+
// attw resolves every entrypoint as a module; assets can never have types.
|
|
257
|
+
// Existence of the target files is still validated by publint.
|
|
258
|
+
return value.endsWith('.wasm') || value.endsWith('.css');
|
|
257
259
|
}
|
|
258
|
-
return isRecord(value) && Object.values(value).some(
|
|
260
|
+
return isRecord(value) && Object.values(value).some(exportPointsToNonJs);
|
|
259
261
|
}
|
|
260
262
|
|
|
261
263
|
async function packPackage(root: string, pkg: PackageInfo): Promise<{ path: string; arrayBuffer: ArrayBuffer }> {
|
package/src/pr/index.test.ts
CHANGED
|
@@ -18,7 +18,7 @@ afterEach(() => {
|
|
|
18
18
|
|
|
19
19
|
const PR_JSON = JSON.stringify({
|
|
20
20
|
number: 40,
|
|
21
|
-
url: 'https://github.com/
|
|
21
|
+
url: 'https://github.com/acme/private/pull/40',
|
|
22
22
|
headRefName: 'gar-sync/private-to-public',
|
|
23
23
|
baseRefName: 'public-mirror',
|
|
24
24
|
isCrossRepository: false,
|
|
@@ -145,7 +145,7 @@ function writeState(overrides: Record<string, unknown>): void {
|
|
|
145
145
|
mkdirSync(join(gitDir, 'smoo'), { recursive: true });
|
|
146
146
|
const state = {
|
|
147
147
|
pr: 40,
|
|
148
|
-
url: 'https://github.com/
|
|
148
|
+
url: 'https://github.com/acme/private/pull/40',
|
|
149
149
|
headBranch: 'gar-sync/private-to-public',
|
|
150
150
|
baseBranch: 'public-mirror',
|
|
151
151
|
remote: 'origin',
|