@smoothbricks/cli 0.6.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/managed-files.d.ts +15 -0
- package/dist/monorepo/managed-files.d.ts.map +1 -1
- package/dist/monorepo/managed-files.js +20 -2
- package/dist/monorepo/packed-package.js +8 -6
- package/package.json +1 -1
- package/src/monorepo/managed-files.test.ts +28 -0
- package/src/monorepo/managed-files.ts +22 -2
- package/src/monorepo/packed-package.ts +8 -6
- package/src/pr/index.test.ts +2 -2
|
@@ -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',
|
|
@@ -114,13 +130,15 @@ function applyManagedFile(root, file, mode, context) {
|
|
|
114
130
|
throw new Error(`${file.target} exists but is not a regular file or symlink`);
|
|
115
131
|
}
|
|
116
132
|
const current = readFileSync(target, 'utf8');
|
|
117
|
-
|
|
133
|
+
const { managed, localTail } = splitLocalSection(current);
|
|
134
|
+
if (managed === content || (localTail !== '' && managed === `${content}\n`)) {
|
|
118
135
|
return { target: file.target, action: 'unchanged' };
|
|
119
136
|
}
|
|
120
137
|
if (mode === 'check' || mode === 'diff') {
|
|
121
138
|
return { target: file.target, action: 'drifted' };
|
|
122
139
|
}
|
|
123
|
-
|
|
140
|
+
const next = localTail === '' ? content : `${content}\n${localTail}`;
|
|
141
|
+
writeManagedFile(target, next, file.executable === true);
|
|
124
142
|
return { target: file.target, action: 'updated' };
|
|
125
143
|
}
|
|
126
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);
|
package/package.json
CHANGED
|
@@ -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';
|
|
@@ -154,13 +172,15 @@ function applyManagedFile(
|
|
|
154
172
|
throw new Error(`${file.target} exists but is not a regular file or symlink`);
|
|
155
173
|
}
|
|
156
174
|
const current = readFileSync(target, 'utf8');
|
|
157
|
-
|
|
175
|
+
const { managed, localTail } = splitLocalSection(current);
|
|
176
|
+
if (managed === content || (localTail !== '' && managed === `${content}\n`)) {
|
|
158
177
|
return { target: file.target, action: 'unchanged' };
|
|
159
178
|
}
|
|
160
179
|
if (mode === 'check' || mode === 'diff') {
|
|
161
180
|
return { target: file.target, action: 'drifted' };
|
|
162
181
|
}
|
|
163
|
-
|
|
182
|
+
const next = localTail === '' ? content : `${content}\n${localTail}`;
|
|
183
|
+
writeManagedFile(target, next, file.executable === true);
|
|
164
184
|
return { target: file.target, action: 'updated' };
|
|
165
185
|
}
|
|
166
186
|
if (mode === 'check' || mode === 'diff') {
|
|
@@ -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',
|