@smoothbricks/cli 0.10.3 → 0.10.5
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/README.md +9 -13
- package/dist/bun/preload.d.ts +13 -0
- package/dist/bun/preload.d.ts.map +1 -0
- package/dist/bun/preload.js +12 -0
- package/dist/cli.js +13 -5
- package/dist/generate/index.d.ts.map +1 -1
- package/dist/generate/index.js +0 -6
- package/dist/github-ci/index.d.ts.map +1 -1
- package/dist/github-ci/index.js +52 -16
- package/dist/github-ci/outputs.d.ts.map +1 -1
- package/dist/github-ci/outputs.js +1 -2
- package/dist/lib/cli-package.js +2 -2
- package/dist/lib/json.d.ts +120 -11
- package/dist/lib/json.d.ts.map +1 -1
- package/dist/lib/json.js +319 -29
- package/dist/lib/workspace.d.ts +7 -14
- package/dist/lib/workspace.d.ts.map +1 -1
- package/dist/lib/workspace.js +76 -82
- package/dist/monorepo/ci-workflow.d.ts +2 -0
- package/dist/monorepo/ci-workflow.d.ts.map +1 -1
- package/dist/monorepo/ci-workflow.js +27 -1
- package/dist/monorepo/git-config.js +2 -2
- package/dist/monorepo/index.d.ts +4 -2
- package/dist/monorepo/index.d.ts.map +1 -1
- package/dist/monorepo/index.js +19 -3
- package/dist/monorepo/lockfile.d.ts +14 -1
- package/dist/monorepo/lockfile.d.ts.map +1 -1
- package/dist/monorepo/lockfile.js +33 -21
- package/dist/monorepo/managed-files.d.ts +11 -0
- package/dist/monorepo/managed-files.d.ts.map +1 -1
- package/dist/monorepo/managed-files.js +57 -32
- package/dist/monorepo/package-policy.d.ts.map +1 -1
- package/dist/monorepo/package-policy.js +52 -45
- package/dist/monorepo/packed-manifest.d.ts +5 -2
- package/dist/monorepo/packed-manifest.d.ts.map +1 -1
- package/dist/monorepo/packed-manifest.js +16 -47
- package/dist/monorepo/packed-package.d.ts.map +1 -1
- package/dist/monorepo/packed-package.js +28 -16
- package/dist/monorepo/packs/index.d.ts.map +1 -1
- package/dist/monorepo/packs/index.js +14 -5
- package/dist/monorepo/publish-workflow.js +3 -1
- package/dist/monorepo/runtime.d.ts +15 -0
- package/dist/monorepo/runtime.d.ts.map +1 -1
- package/dist/monorepo/runtime.js +66 -12
- package/dist/monorepo/tool-validation.d.ts +2 -2
- package/dist/monorepo/tool-validation.d.ts.map +1 -1
- package/dist/monorepo/tool-validation.js +141 -38
- package/dist/monorepo/wrangler.d.ts.map +1 -1
- package/dist/monorepo/wrangler.js +13 -13
- package/dist/nx/index.d.ts +9 -7
- package/dist/nx/index.d.ts.map +1 -1
- package/dist/nx/index.js +48 -31
- package/dist/pr/index.d.ts.map +1 -1
- package/dist/pr/index.js +28 -32
- package/dist/release/candidates.d.ts +3 -2
- package/dist/release/candidates.d.ts.map +1 -1
- package/dist/release/candidates.js +4 -5
- package/dist/release/index.d.ts +1 -0
- package/dist/release/index.d.ts.map +1 -1
- package/dist/release/index.js +88 -59
- package/dist/wrangler/prepare-env.d.ts +0 -1
- package/dist/wrangler/prepare-env.d.ts.map +1 -1
- package/dist/wrangler/prepare-env.js +100 -51
- package/dist/wrangler/scaffold.d.ts.map +1 -1
- package/dist/wrangler/scaffold.js +12 -12
- package/managed/raw/envrc +5 -3
- package/managed/raw/git-format-staged.yml +19 -1
- package/managed/raw/patches/ttsc@0.19.3.patch +67 -0
- package/managed/raw/tooling/direnv/github-actions-bootstrap.sh +2 -1
- package/managed/raw/tooling/direnv/merge-newer-pins.sh +3 -3
- package/managed/raw/tooling/direnv/setup-environment.ts +170 -19
- package/managed/raw/tooling/git-hooks/pre-commit.sh +0 -6
- package/managed/templates/github/actions/cache-node-modules/action.yml +5 -2
- package/managed/templates/github/actions/cache-ttsc-plugins/action.yml +19 -0
- package/managed/templates/github/actions/setup-devenv/action.yml +5 -0
- package/managed/templates/github/workflows/managed-files.yml +80 -0
- package/package.json +15 -4
- package/src/bun/preload.ts +12 -0
- package/src/cli.ts +20 -8
- package/src/generate/index.ts +0 -6
- package/src/github-ci/index.ts +49 -18
- package/src/github-ci/outputs.ts +1 -2
- package/src/lib/cli-package.ts +2 -2
- package/src/lib/json.ts +293 -32
- package/src/lib/workspace.ts +88 -100
- package/src/monorepo/__tests__/ci-workflow.test.ts +3 -3
- package/src/monorepo/ci-workflow.ts +27 -1
- package/src/monorepo/git-config.ts +2 -2
- package/src/monorepo/index.ts +19 -3
- package/src/monorepo/lockfile.test.ts +35 -36
- package/src/monorepo/lockfile.ts +49 -24
- package/src/monorepo/managed-files.test.ts +1 -0
- package/src/monorepo/managed-files.ts +62 -38
- package/src/monorepo/package-policy.test.ts +20 -50
- package/src/monorepo/package-policy.ts +76 -59
- package/src/monorepo/packed-manifest.ts +18 -52
- package/src/monorepo/packed-package.ts +43 -22
- package/src/monorepo/packs/index.ts +14 -5
- package/src/monorepo/publish-workflow.ts +3 -1
- package/src/monorepo/runtime.test.ts +42 -3
- package/src/monorepo/runtime.ts +90 -12
- package/src/monorepo/tool-validation.test.ts +38 -16
- package/src/monorepo/tool-validation.ts +149 -41
- package/src/monorepo/wrangler.test.ts +11 -15
- package/src/monorepo/wrangler.ts +15 -15
- package/src/nx/index.test.ts +9 -5
- package/src/nx/index.ts +69 -42
- package/src/pr/index.ts +42 -36
- package/src/release/__tests__/candidates.test.ts +5 -4
- package/src/release/__tests__/trust-publisher.test.ts +7 -0
- package/src/release/candidates.ts +10 -17
- package/src/release/index.ts +110 -73
- package/src/wrangler/prepare-env.ts +75 -54
- package/src/wrangler/scaffold.test.ts +9 -12
- package/src/wrangler/scaffold.ts +13 -13
package/src/lib/workspace.ts
CHANGED
|
@@ -1,7 +1,16 @@
|
|
|
1
1
|
import { readdirSync, statSync } from 'node:fs';
|
|
2
2
|
import { dirname, join, relative } from 'node:path';
|
|
3
3
|
import { isSmoothBricksCodebasePackageName } from './cli-package.js';
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
isPackageJson,
|
|
6
|
+
type PackageJson,
|
|
7
|
+
type PackageNxConfig,
|
|
8
|
+
type PackageRepository,
|
|
9
|
+
readJson,
|
|
10
|
+
readJsonObject,
|
|
11
|
+
} from './json.js';
|
|
12
|
+
|
|
13
|
+
export type { PackageJson, PackageNxConfig, PackageRepository };
|
|
5
14
|
|
|
6
15
|
export interface PackageInfo {
|
|
7
16
|
name: string;
|
|
@@ -11,7 +20,7 @@ export interface PackageInfo {
|
|
|
11
20
|
tags: string[];
|
|
12
21
|
path: string;
|
|
13
22
|
packageJsonPath: string;
|
|
14
|
-
json:
|
|
23
|
+
json: PackageJson;
|
|
15
24
|
}
|
|
16
25
|
|
|
17
26
|
export interface WorkspacePackageManifest {
|
|
@@ -20,18 +29,7 @@ export interface WorkspacePackageManifest {
|
|
|
20
29
|
private: boolean;
|
|
21
30
|
path: string;
|
|
22
31
|
packageJsonPath: string;
|
|
23
|
-
json:
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export interface PackageJson extends Record<string, unknown> {
|
|
27
|
-
name?: string;
|
|
28
|
-
version?: string;
|
|
29
|
-
private?: boolean;
|
|
30
|
-
workspaces?: unknown;
|
|
31
|
-
devDependencies?: unknown;
|
|
32
|
-
dependencies?: unknown;
|
|
33
|
-
nx?: unknown;
|
|
34
|
-
repository?: unknown;
|
|
32
|
+
json: PackageJson;
|
|
35
33
|
}
|
|
36
34
|
|
|
37
35
|
export interface RepositoryInfo {
|
|
@@ -46,6 +44,8 @@ export const workspaceDependencyFields = [
|
|
|
46
44
|
'optionalDependencies',
|
|
47
45
|
] as const;
|
|
48
46
|
|
|
47
|
+
export type WorkspaceDependencyField = (typeof workspaceDependencyFields)[number];
|
|
48
|
+
|
|
49
49
|
export function listPublicPackages(root: string): PackageInfo[] {
|
|
50
50
|
return getWorkspacePackages(root).filter((pkg) => !pkg.private && pkg.tags.includes('npm:public'));
|
|
51
51
|
}
|
|
@@ -54,26 +54,21 @@ export function listReleasePackages(
|
|
|
54
54
|
root: string,
|
|
55
55
|
rootPackage = readPackageJson(join(root, 'package.json')),
|
|
56
56
|
): PackageInfo[] {
|
|
57
|
-
|
|
58
|
-
return [];
|
|
59
|
-
}
|
|
60
|
-
const rootRepository = repositoryInfo(rootPackage.json);
|
|
57
|
+
const rootRepository = rootPackage ? repositoryInfo(rootPackage.json) : null;
|
|
61
58
|
if (!rootRepository) {
|
|
62
59
|
return [];
|
|
63
60
|
}
|
|
64
|
-
return
|
|
65
|
-
(pkg) => !pkg.private && pkg.tags.includes('npm:public') && isOwnedPackage(rootRepository, pkg),
|
|
66
|
-
);
|
|
61
|
+
return listPublicPackages(root).filter((pkg) => isOwnedPackage(rootRepository, pkg));
|
|
67
62
|
}
|
|
68
63
|
|
|
69
64
|
export function rootRepositoryInfo(root: string): RepositoryInfo | null {
|
|
70
|
-
const rootPackage =
|
|
71
|
-
return rootPackage ? repositoryInfo(rootPackage) : null;
|
|
65
|
+
const rootPackage = readPackageJson(join(root, 'package.json'));
|
|
66
|
+
return rootPackage ? repositoryInfo(rootPackage.json) : null;
|
|
72
67
|
}
|
|
73
68
|
|
|
74
69
|
export function rootPackageName(root: string): string | null {
|
|
75
70
|
const rootPackage = readPackageJsonObject(join(root, 'package.json'));
|
|
76
|
-
return rootPackage
|
|
71
|
+
return rootPackage?.name ?? null;
|
|
77
72
|
}
|
|
78
73
|
|
|
79
74
|
export function isSmoothBricksCodebase(root: string): boolean {
|
|
@@ -90,37 +85,23 @@ export function isOwnedPackage(rootRepository: RepositoryInfo, pkg: PackageInfo)
|
|
|
90
85
|
}
|
|
91
86
|
|
|
92
87
|
export function getWorkspacePackages(root: string): PackageInfo[] {
|
|
93
|
-
|
|
94
|
-
if (!rootPackage) {
|
|
95
|
-
throw new Error('package.json not found or invalid');
|
|
96
|
-
}
|
|
97
|
-
return getWorkspacePackagesForPatterns(root, getWorkspacePatternsFromPackageJson(rootPackage.json));
|
|
88
|
+
return getWorkspacePackagesForPatterns(root, getWorkspacePatterns(root));
|
|
98
89
|
}
|
|
99
90
|
|
|
100
91
|
export function getWorkspacePackageManifests(root: string): WorkspacePackageManifest[] {
|
|
101
|
-
|
|
102
|
-
if (!rootPackage) {
|
|
103
|
-
throw new Error('package.json not found or invalid');
|
|
104
|
-
}
|
|
105
|
-
return getWorkspacePackageManifestsForPatterns(root, getWorkspacePatternsFromPackageJson(rootPackage));
|
|
92
|
+
return getWorkspacePackageManifestsForPatterns(root, getWorkspacePatterns(root));
|
|
106
93
|
}
|
|
107
94
|
|
|
108
95
|
function getWorkspacePackagesForPatterns(root: string, workspacePatterns: string[]): PackageInfo[] {
|
|
109
96
|
const packages: PackageInfo[] = [];
|
|
110
|
-
for (const
|
|
111
|
-
const pkg = readPackageJson(
|
|
112
|
-
if (!pkg
|
|
97
|
+
for (const path of listWorkspacePackageJsonPaths(root, workspacePatterns)) {
|
|
98
|
+
const pkg = readPackageJson(path);
|
|
99
|
+
if (!pkg) {
|
|
113
100
|
continue;
|
|
114
101
|
}
|
|
115
102
|
packages.push({
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
version: pkg.version,
|
|
119
|
-
private: pkg.private,
|
|
120
|
-
tags: pkg.tags,
|
|
121
|
-
path: relative(root, dirname(pkgPath)),
|
|
122
|
-
packageJsonPath: pkg.packageJsonPath,
|
|
123
|
-
json: pkg.json,
|
|
103
|
+
...pkg,
|
|
104
|
+
path: relative(root, dirname(path)),
|
|
124
105
|
});
|
|
125
106
|
}
|
|
126
107
|
return packages.sort((a, b) => a.name.localeCompare(b.name));
|
|
@@ -130,30 +111,42 @@ function getWorkspacePackageManifestsForPatterns(
|
|
|
130
111
|
root: string,
|
|
131
112
|
workspacePatterns: string[],
|
|
132
113
|
): WorkspacePackageManifest[] {
|
|
133
|
-
const
|
|
134
|
-
for (const
|
|
135
|
-
const pkg = readWorkspacePackageManifest(
|
|
136
|
-
if (pkg) {
|
|
137
|
-
|
|
114
|
+
const packages: WorkspacePackageManifest[] = [];
|
|
115
|
+
for (const path of listWorkspacePackageJsonPaths(root, workspacePatterns)) {
|
|
116
|
+
const pkg = readWorkspacePackageManifest(path);
|
|
117
|
+
if (!pkg) {
|
|
118
|
+
continue;
|
|
138
119
|
}
|
|
120
|
+
packages.push({
|
|
121
|
+
...pkg,
|
|
122
|
+
path: relative(root, dirname(path)),
|
|
123
|
+
});
|
|
139
124
|
}
|
|
140
|
-
return
|
|
125
|
+
return packages.sort((a, b) => a.name.localeCompare(b.name));
|
|
141
126
|
}
|
|
142
127
|
|
|
143
128
|
function listWorkspacePackageJsonPaths(root: string, workspacePatterns: string[]): string[] {
|
|
144
129
|
const paths: string[] = [];
|
|
145
130
|
for (const pattern of workspacePatterns) {
|
|
146
131
|
if (!pattern.endsWith('/*')) {
|
|
147
|
-
paths.push(join(root, pattern, 'package.json'));
|
|
148
132
|
continue;
|
|
149
133
|
}
|
|
150
|
-
|
|
151
134
|
const parent = join(root, pattern.slice(0, -2));
|
|
152
|
-
|
|
135
|
+
let entries: string[];
|
|
136
|
+
try {
|
|
137
|
+
entries = readdirSync(parent);
|
|
138
|
+
} catch {
|
|
153
139
|
continue;
|
|
154
140
|
}
|
|
155
|
-
for (const entry of
|
|
156
|
-
|
|
141
|
+
for (const entry of entries) {
|
|
142
|
+
const packageJsonPath = join(parent, entry, 'package.json');
|
|
143
|
+
try {
|
|
144
|
+
if (statSync(packageJsonPath).isFile()) {
|
|
145
|
+
paths.push(packageJsonPath);
|
|
146
|
+
}
|
|
147
|
+
} catch {
|
|
148
|
+
// skip missing package.json
|
|
149
|
+
}
|
|
157
150
|
}
|
|
158
151
|
}
|
|
159
152
|
return paths;
|
|
@@ -162,7 +155,7 @@ function listWorkspacePackageJsonPaths(root: string, workspacePatterns: string[]
|
|
|
162
155
|
export function listPackageJsonRecords(root: string): PackageInfo[] {
|
|
163
156
|
const rootPackage = readPackageJson(join(root, 'package.json'));
|
|
164
157
|
if (!rootPackage) {
|
|
165
|
-
|
|
158
|
+
return getWorkspacePackages(root);
|
|
166
159
|
}
|
|
167
160
|
return [
|
|
168
161
|
{ ...rootPackage, path: '.' },
|
|
@@ -172,18 +165,16 @@ export function listPackageJsonRecords(root: string): PackageInfo[] {
|
|
|
172
165
|
|
|
173
166
|
export function getWorkspacePatterns(root: string): string[] {
|
|
174
167
|
const raw = readJson(join(root, 'package.json'));
|
|
175
|
-
|
|
168
|
+
const pkg = isPackageJson(raw) ? raw : null;
|
|
169
|
+
return pkg ? getWorkspacePatternsFromPackageJson(pkg) : ['packages/*'];
|
|
176
170
|
}
|
|
177
171
|
|
|
178
|
-
function getWorkspacePatternsFromPackageJson(pkg:
|
|
179
|
-
if (!hasOwn(pkg, 'workspaces')) {
|
|
180
|
-
return ['packages/*'];
|
|
181
|
-
}
|
|
172
|
+
function getWorkspacePatternsFromPackageJson(pkg: PackageJson): string[] {
|
|
182
173
|
const workspaces = pkg.workspaces;
|
|
183
174
|
if (Array.isArray(workspaces)) {
|
|
184
175
|
return workspaces.filter((entry): entry is string => typeof entry === 'string');
|
|
185
176
|
}
|
|
186
|
-
if (
|
|
177
|
+
if (workspaces && typeof workspaces === 'object' && Array.isArray(workspaces.packages)) {
|
|
187
178
|
return workspaces.packages.filter((entry): entry is string => typeof entry === 'string');
|
|
188
179
|
}
|
|
189
180
|
return ['packages/*'];
|
|
@@ -191,17 +182,15 @@ function getWorkspacePatternsFromPackageJson(pkg: Record<string, unknown>): stri
|
|
|
191
182
|
|
|
192
183
|
export function readPackageJson(path: string): PackageInfo | null {
|
|
193
184
|
const parsed = readPackageJsonObject(path);
|
|
194
|
-
if (!
|
|
185
|
+
if (!parsed?.name || !parsed.version) {
|
|
195
186
|
return null;
|
|
196
187
|
}
|
|
197
|
-
const privateValue = hasOwn(parsed, 'private') && typeof parsed.private === 'boolean' ? parsed.private : false;
|
|
198
|
-
const tags = getNxTags(parsed);
|
|
199
188
|
return {
|
|
200
189
|
name: parsed.name,
|
|
201
190
|
projectName: packageNxProjectName(parsed),
|
|
202
191
|
version: parsed.version,
|
|
203
|
-
private:
|
|
204
|
-
tags,
|
|
192
|
+
private: parsed.private === true,
|
|
193
|
+
tags: getNxTags(parsed),
|
|
205
194
|
path: dirname(path),
|
|
206
195
|
packageJsonPath: path,
|
|
207
196
|
json: parsed,
|
|
@@ -210,50 +199,48 @@ export function readPackageJson(path: string): PackageInfo | null {
|
|
|
210
199
|
|
|
211
200
|
export function readWorkspacePackageManifest(path: string): WorkspacePackageManifest | null {
|
|
212
201
|
const parsed = readPackageJsonObject(path);
|
|
213
|
-
if (!
|
|
202
|
+
if (!parsed?.name) {
|
|
214
203
|
return null;
|
|
215
204
|
}
|
|
216
|
-
const privateValue = hasOwn(parsed, 'private') && typeof parsed.private === 'boolean' ? parsed.private : false;
|
|
217
205
|
return {
|
|
218
206
|
name: parsed.name,
|
|
219
207
|
projectName: packageNxProjectName(parsed),
|
|
220
|
-
private:
|
|
208
|
+
private: parsed.private === true,
|
|
221
209
|
path: dirname(path),
|
|
222
210
|
packageJsonPath: path,
|
|
223
211
|
json: parsed,
|
|
224
212
|
};
|
|
225
213
|
}
|
|
226
214
|
|
|
227
|
-
function packageNxProjectName(pkg:
|
|
228
|
-
|
|
229
|
-
return (nx ? stringProperty(nx, 'name') : null) ?? stringProperty(pkg, 'name') ?? '';
|
|
215
|
+
function packageNxProjectName(pkg: PackageJson): string {
|
|
216
|
+
return pkg.nx?.name ?? pkg.name ?? '';
|
|
230
217
|
}
|
|
231
218
|
|
|
232
219
|
export function readPackageJsonObject(path: string): PackageJson | null {
|
|
233
|
-
|
|
234
|
-
return parsed;
|
|
220
|
+
return readJsonObject(path);
|
|
235
221
|
}
|
|
236
222
|
|
|
237
|
-
export function getNxTags(pkg:
|
|
238
|
-
|
|
223
|
+
export function getNxTags(pkg: PackageJson): string[] {
|
|
224
|
+
const tags = pkg.nx?.tags;
|
|
225
|
+
if (!Array.isArray(tags)) {
|
|
239
226
|
return [];
|
|
240
227
|
}
|
|
241
|
-
return
|
|
228
|
+
return tags.filter((tag): tag is string => typeof tag === 'string');
|
|
242
229
|
}
|
|
243
230
|
|
|
244
|
-
export function repositoryInfo(pkg:
|
|
231
|
+
export function repositoryInfo(pkg: PackageJson): RepositoryInfo | null {
|
|
245
232
|
const repository = pkg.repository;
|
|
246
233
|
if (typeof repository === 'string') {
|
|
247
234
|
return { type: 'git', url: repository };
|
|
248
235
|
}
|
|
249
|
-
if (!
|
|
236
|
+
if (!repository || typeof repository !== 'object') {
|
|
250
237
|
return null;
|
|
251
238
|
}
|
|
252
|
-
const url =
|
|
239
|
+
const url = repository.url;
|
|
253
240
|
if (!url) {
|
|
254
241
|
return null;
|
|
255
242
|
}
|
|
256
|
-
return { type:
|
|
243
|
+
return { type: repository.type ?? 'git', url };
|
|
257
244
|
}
|
|
258
245
|
|
|
259
246
|
export function sameRepositoryAfterNormalization(left: string, right: string): boolean {
|
|
@@ -261,25 +248,26 @@ export function sameRepositoryAfterNormalization(left: string, right: string): b
|
|
|
261
248
|
}
|
|
262
249
|
|
|
263
250
|
function normalizedRepositoryUrl(url: string): string {
|
|
264
|
-
const trimmed = url
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
.
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
if (https?.[1] && https[2]) {
|
|
272
|
-
return githubRepositoryKey(https[1], https[2]);
|
|
273
|
-
}
|
|
274
|
-
const ssh = /^git@github\.com:([^/]+)\/([^/]+)$/i.exec(trimmed);
|
|
275
|
-
if (ssh?.[1] && ssh[2]) {
|
|
276
|
-
return githubRepositoryKey(ssh[1], ssh[2]);
|
|
251
|
+
const trimmed = url.trim().replace(/\.git$/i, '');
|
|
252
|
+
const ssh = /^git@github\.com:(.+)$/i.exec(trimmed);
|
|
253
|
+
if (ssh?.[1]) {
|
|
254
|
+
const [owner, repo] = ssh[1].split('/');
|
|
255
|
+
if (owner && repo) {
|
|
256
|
+
return githubRepositoryKey(owner, repo);
|
|
257
|
+
}
|
|
277
258
|
}
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
259
|
+
try {
|
|
260
|
+
const parsed = new URL(trimmed.includes('://') ? trimmed : `https://${trimmed}`);
|
|
261
|
+
if (parsed.hostname.replace(/^www\./i, '').toLowerCase() === 'github.com') {
|
|
262
|
+
const [owner, repo] = parsed.pathname.replace(/^\//, '').split('/');
|
|
263
|
+
if (owner && repo) {
|
|
264
|
+
return githubRepositoryKey(owner, repo);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
} catch {
|
|
268
|
+
// fall through
|
|
281
269
|
}
|
|
282
|
-
return trimmed;
|
|
270
|
+
return trimmed.toLowerCase();
|
|
283
271
|
}
|
|
284
272
|
|
|
285
273
|
function githubRepositoryKey(owner: string, repo: string): string {
|
|
@@ -17,14 +17,14 @@ describe('CI workflow definition', () => {
|
|
|
17
17
|
const steps = defineCiWorkflow({ deploy: true, pushBranches: ['main'] });
|
|
18
18
|
const rendered = renderCiWorkflowYaml({ deploy: true, pushBranches: ['main'] });
|
|
19
19
|
|
|
20
|
-
expect(steps.map((step) => [step.kind, step.number])).toContainEqual([CiWorkflowStepKind.Deploy,
|
|
20
|
+
expect(steps.map((step) => [step.kind, step.number])).toContainEqual([CiWorkflowStepKind.Deploy, 11]);
|
|
21
21
|
expect(rendered).toContain('- name: 🚀 Deploy Staging');
|
|
22
22
|
expect(rendered).not.toContain('CLOUDFLARE_API_TOKEN');
|
|
23
23
|
expect(rendered).not.toContain('CLOUDFLARE_ACCOUNT_ID');
|
|
24
24
|
expect(rendered).toContain(
|
|
25
|
-
'smoo github-ci nx-deploy --configuration staging --mode affected --name "Deploy Staging" --step
|
|
25
|
+
'smoo github-ci nx-deploy --configuration staging --mode affected --name "Deploy Staging" --step 11',
|
|
26
26
|
);
|
|
27
|
-
expect(rendered).toContain("# Step
|
|
27
|
+
expect(rendered).toContain("# Step 12\n # Nx's database cache needs artifact files");
|
|
28
28
|
});
|
|
29
29
|
|
|
30
30
|
it('adds Cloudflare credentials for Wrangler-backed deploys', () => {
|
|
@@ -8,6 +8,8 @@ export enum CiWorkflowStepKind {
|
|
|
8
8
|
Build = 'build',
|
|
9
9
|
Lint = 'lint',
|
|
10
10
|
UnitTests = 'unit-tests',
|
|
11
|
+
ManagedFilesCheck = 'managed-files-check',
|
|
12
|
+
ManagedFilesDispatch = 'managed-files-dispatch',
|
|
11
13
|
Deploy = 'deploy',
|
|
12
14
|
SaveNxCache = 'save-nx-cache',
|
|
13
15
|
UploadTraceDbs = 'upload-trace-dbs',
|
|
@@ -37,6 +39,8 @@ export function defineCiWorkflow(options: CiWorkflowDefinitionOptions): CiWorkfl
|
|
|
37
39
|
{ kind: CiWorkflowStepKind.Build, name: '🔨 Build' },
|
|
38
40
|
{ kind: CiWorkflowStepKind.Lint, name: '🔍 Lint' },
|
|
39
41
|
{ kind: CiWorkflowStepKind.UnitTests, name: '🧪 Unit Tests' },
|
|
42
|
+
{ kind: CiWorkflowStepKind.ManagedFilesCheck, name: '🩺 Check managed-file drift' },
|
|
43
|
+
{ kind: CiWorkflowStepKind.ManagedFilesDispatch, name: '🔁 Dispatch managed-file drift healing' },
|
|
40
44
|
];
|
|
41
45
|
if (options.deploy) {
|
|
42
46
|
steps.push({ kind: CiWorkflowStepKind.Deploy, name: '🚀 Deploy Staging' });
|
|
@@ -64,7 +68,8 @@ ${renderYamlList(options.pushBranches, 6)}
|
|
|
64
68
|
pull_request:
|
|
65
69
|
|
|
66
70
|
permissions:
|
|
67
|
-
actions:
|
|
71
|
+
# actions:write lets the drift step dispatch the managed-files workflow.
|
|
72
|
+
actions: write
|
|
68
73
|
contents: read
|
|
69
74
|
statuses: write
|
|
70
75
|
|
|
@@ -161,6 +166,27 @@ function yamlLinesForStep(step: CiWorkflowStep, options: CiWorkflowDefinitionOpt
|
|
|
161
166
|
return nxSmartStep(step, 'lint', 'Lint');
|
|
162
167
|
case CiWorkflowStepKind.UnitTests:
|
|
163
168
|
return nxSmartStep(step, 'test', 'Unit Tests');
|
|
169
|
+
case CiWorkflowStepKind.ManagedFilesCheck:
|
|
170
|
+
// The authoritative drift check is nearly free here (devenv is already
|
|
171
|
+
// up). --warn never fails the job and publishes the step output
|
|
172
|
+
// `drifted=<count>` for the dispatch step below.
|
|
173
|
+
return [
|
|
174
|
+
` - name: ${step.name}`,
|
|
175
|
+
' id: managed-drift',
|
|
176
|
+
' if:',
|
|
177
|
+
" ${{ github.event_name == 'push' && github.ref == format('refs/heads/{0}',",
|
|
178
|
+
' github.event.repository.default_branch) }}',
|
|
179
|
+
' run: smoo monorepo check --warn',
|
|
180
|
+
];
|
|
181
|
+
case CiWorkflowStepKind.ManagedFilesDispatch:
|
|
182
|
+
// Dispatches the managed-files workflow, which maintains the persistent
|
|
183
|
+
// review PR. workflow_dispatch is exempt from GitHub's GITHUB_TOKEN
|
|
184
|
+
// recursion guard, so the default token suffices.
|
|
185
|
+
return [
|
|
186
|
+
` - name: ${step.name}`,
|
|
187
|
+
" if: ${{ steps.managed-drift.outputs.drifted != '' && steps.managed-drift.outputs.drifted != '0' }}",
|
|
188
|
+
' run: gh workflow run managed-files.yml --ref "$GITHUB_REF_NAME"',
|
|
189
|
+
];
|
|
164
190
|
case CiWorkflowStepKind.Deploy:
|
|
165
191
|
return [
|
|
166
192
|
` - name: ${step.name}`,
|
|
@@ -17,8 +17,8 @@ export async function applyWorkspaceGitConfig(root: string): Promise<void> {
|
|
|
17
17
|
// Keep the newer runtime version pins on any merge (nvfetcher overlay +
|
|
18
18
|
// devenv.lock) so a mirror sync's `git am --3way` never stalls on a version
|
|
19
19
|
// conflict. Mapped by the managed .gitattributes (merge=smoo-newer-pins);
|
|
20
|
-
// implemented in tooling/direnv/merge-newer-pins.sh.
|
|
21
|
-
//
|
|
20
|
+
// implemented in tooling/direnv/merge-newer-pins.sh. Runtime package.json
|
|
21
|
+
// pin repair is explicit via `smoo monorepo init --runtime-only`.
|
|
22
22
|
await $`git config --local merge.smoo-newer-pins.name ${'keep the newer devenv/nvfetcher runtime pins'}`.cwd(root);
|
|
23
23
|
await $`git config --local merge.smoo-newer-pins.driver ${'bash tooling/direnv/merge-newer-pins.sh %O %A %B %P'}`.cwd(
|
|
24
24
|
root,
|
package/src/monorepo/index.ts
CHANGED
|
@@ -5,10 +5,11 @@ import { escapeRegex, getWorkspacePackages, getWorkspacePatterns, listReleasePac
|
|
|
5
5
|
import { formatCommitMessage, validateCommitMessage } from './commit-msg.js';
|
|
6
6
|
import { applyWorkspaceGitConfig } from './git-config.js';
|
|
7
7
|
import { syncBunLockfileVersions } from './lockfile.js';
|
|
8
|
-
import { applyManagedFiles, printResults } from './managed-files.js';
|
|
8
|
+
import { applyManagedFiles, printResults, warnOnManagedFileDrift } from './managed-files.js';
|
|
9
9
|
import { listValidCommitScopes, validatePublicTags } from './package-policy.js';
|
|
10
10
|
import { runInitPacks, runValidatePacks } from './packs/index.js';
|
|
11
11
|
import { syncRootRuntimeVersions } from './runtime.js';
|
|
12
|
+
import { applyToolConfigDefaults } from './tool-validation.js';
|
|
12
13
|
|
|
13
14
|
export interface InitOptions {
|
|
14
15
|
runtimeOnly?: boolean;
|
|
@@ -74,11 +75,26 @@ export async function validateMonorepo(root: string, options: ValidateOptions =
|
|
|
74
75
|
}
|
|
75
76
|
}
|
|
76
77
|
|
|
77
|
-
export function updateManagedFiles(root: string): void {
|
|
78
|
+
export async function updateManagedFiles(root: string): Promise<void> {
|
|
78
79
|
printResults(applyManagedFiles(root, 'update'));
|
|
80
|
+
// Tool dependency policy (typescript API 6, @typescript/native for ttsc, nx, …)
|
|
81
|
+
// lives next to managed templates — update must install them, not only rewrite files.
|
|
82
|
+
await applyToolConfigDefaults(root);
|
|
83
|
+
syncBunLockfileVersions(root, { mode: 'install' });
|
|
84
|
+
console.log('installing workspace dependencies (bun install)');
|
|
85
|
+
const install = await $`bun install --no-summary`.cwd(root).nothrow();
|
|
86
|
+
if (install.exitCode !== 0) {
|
|
87
|
+
const stderr = decode(install.stderr).trim();
|
|
88
|
+
const stdout = decode(install.stdout).trim();
|
|
89
|
+
throw new Error(`bun install failed after monorepo update${stderr || stdout ? `:\n${stderr || stdout}` : ''}`);
|
|
90
|
+
}
|
|
79
91
|
}
|
|
80
92
|
|
|
81
|
-
export function checkManagedFiles(root: string): void {
|
|
93
|
+
export function checkManagedFiles(root: string, options: { warn?: boolean } = {}): void {
|
|
94
|
+
if (options.warn === true) {
|
|
95
|
+
warnOnManagedFileDrift(root);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
82
98
|
const results = applyManagedFiles(root, 'check');
|
|
83
99
|
printResults(results);
|
|
84
100
|
if (results.some((result) => result.action === 'drifted')) {
|
|
@@ -17,15 +17,6 @@ function git(root: string, args: string): string {
|
|
|
17
17
|
return execSync(`git ${args}`, {
|
|
18
18
|
cwd: root,
|
|
19
19
|
encoding: 'utf8',
|
|
20
|
-
env: {
|
|
21
|
-
...process.env,
|
|
22
|
-
GIT_CONFIG_GLOBAL: '/dev/null',
|
|
23
|
-
GIT_CONFIG_SYSTEM: '/dev/null',
|
|
24
|
-
GIT_AUTHOR_NAME: 'fixture',
|
|
25
|
-
GIT_AUTHOR_EMAIL: 'fixture@invalid',
|
|
26
|
-
GIT_COMMITTER_NAME: 'fixture',
|
|
27
|
-
GIT_COMMITTER_EMAIL: 'fixture@invalid',
|
|
28
|
-
},
|
|
29
20
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
30
21
|
});
|
|
31
22
|
}
|
|
@@ -40,6 +31,9 @@ function makeFixtureRepo(options: FixtureOptions): string {
|
|
|
40
31
|
const root = mkdtempSync(join(tmpdir(), 'smoo-lockfile-'));
|
|
41
32
|
cleanupRoots.push(root);
|
|
42
33
|
git(root, 'init -q');
|
|
34
|
+
// Local identity only — CI runners have no global git user.
|
|
35
|
+
git(root, 'config user.name Test');
|
|
36
|
+
git(root, 'config user.email test@example.com');
|
|
43
37
|
git(root, 'commit -q --allow-empty -m init');
|
|
44
38
|
writeFileSync(
|
|
45
39
|
join(root, 'package.json'),
|
|
@@ -56,8 +50,8 @@ function makeFixtureRepo(options: FixtureOptions): string {
|
|
|
56
50
|
|
|
57
51
|
function writeFixturePackage(root: string, version: string): void {
|
|
58
52
|
writeFileSync(
|
|
59
|
-
join(root, 'packages
|
|
60
|
-
JSON.stringify({ name: '@fixture/foo', version, nx: { name: 'foo' } })
|
|
53
|
+
join(root, 'packages/foo/package.json'),
|
|
54
|
+
`${JSON.stringify({ name: '@fixture/foo', version, private: false, nx: { name: 'foo' } }, null, 2)}\n`,
|
|
61
55
|
);
|
|
62
56
|
}
|
|
63
57
|
|
|
@@ -71,9 +65,9 @@ function writeFixtureLock(root: string, version: string): void {
|
|
|
71
65
|
' "": { "name": "fixture-root" },',
|
|
72
66
|
' "packages/foo": {',
|
|
73
67
|
' "name": "@fixture/foo",',
|
|
74
|
-
` "version": "${version}"
|
|
75
|
-
' }
|
|
76
|
-
' }
|
|
68
|
+
` "version": "${version}"`,
|
|
69
|
+
' }',
|
|
70
|
+
' }',
|
|
77
71
|
'}',
|
|
78
72
|
'',
|
|
79
73
|
].join('\n'),
|
|
@@ -87,59 +81,64 @@ function lockVersionIn(root: string): string {
|
|
|
87
81
|
}
|
|
88
82
|
|
|
89
83
|
describe('bun.lock workspace version sync', () => {
|
|
90
|
-
it('
|
|
91
|
-
// The b3298fc4c shape: package.json holds the next prerelease, a stable tag
|
|
92
|
-
// exists, and `bun install` rewrote the lockfile back to the prerelease.
|
|
84
|
+
it('install validate requires lockfile ≡ package.json including -next', () => {
|
|
93
85
|
const root = makeFixtureRepo({
|
|
94
86
|
packageVersion: '0.1.3-next.0',
|
|
95
|
-
lockVersion: '0.1.
|
|
87
|
+
lockVersion: '0.1.2',
|
|
96
88
|
stableTag: 'foo@0.1.2',
|
|
97
89
|
});
|
|
98
90
|
expect(validateBunLockfileVersions(root)).toBe(1);
|
|
99
91
|
});
|
|
100
92
|
|
|
101
|
-
it('sync restores
|
|
93
|
+
it('install mode sync restores package.json -next for frozen CI', () => {
|
|
94
|
+
const root = makeFixtureRepo({
|
|
95
|
+
packageVersion: '0.1.3-next.0',
|
|
96
|
+
lockVersion: '0.1.2',
|
|
97
|
+
stableTag: 'foo@0.1.2',
|
|
98
|
+
});
|
|
99
|
+
expect(syncBunLockfileVersions(root, { log: false, mode: 'install' })).toBe(1);
|
|
100
|
+
expect(lockVersionIn(root)).toBe('0.1.3-next.0');
|
|
101
|
+
expect(validateBunLockfileVersions(root)).toBe(0);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('publish mode rewrites unpublished -next to last stable tag for pack', () => {
|
|
102
105
|
const root = makeFixtureRepo({
|
|
103
106
|
packageVersion: '0.1.3-next.0',
|
|
104
107
|
lockVersion: '0.1.3-next.0',
|
|
105
108
|
stableTag: 'foo@0.1.2',
|
|
106
109
|
});
|
|
107
|
-
|
|
108
|
-
expect(lockVersionIn(root)).toBe('0.1.2');
|
|
110
|
+
// Install/CI is happy with -next matching package.json.
|
|
109
111
|
expect(validateBunLockfileVersions(root)).toBe(0);
|
|
112
|
+
// Pre-publish rewrite embeds last stable for bun pm pack.
|
|
113
|
+
expect(syncBunLockfileVersions(root, { log: false, mode: 'publish' })).toBe(1);
|
|
114
|
+
expect(lockVersionIn(root)).toBe('0.1.2');
|
|
110
115
|
});
|
|
111
116
|
|
|
112
|
-
it('
|
|
113
|
-
// The new-workspace-package shape (e.g. cowshed): no stable tag exists yet.
|
|
117
|
+
it('never-published prerelease keeps package.json version in both modes', () => {
|
|
114
118
|
const root = makeFixtureRepo({ packageVersion: '0.2.0-next.0', lockVersion: '0.2.0-next.0' });
|
|
115
119
|
expect(validateBunLockfileVersions(root)).toBe(0);
|
|
116
|
-
expect(syncBunLockfileVersions(root, { log: false })).toBe(0);
|
|
120
|
+
expect(syncBunLockfileVersions(root, { log: false, mode: 'install' })).toBe(0);
|
|
121
|
+
expect(syncBunLockfileVersions(root, { log: false, mode: 'publish' })).toBe(0);
|
|
117
122
|
});
|
|
118
123
|
|
|
119
|
-
it('
|
|
124
|
+
it('stable manifest version is required verbatim', () => {
|
|
120
125
|
const root = makeFixtureRepo({ packageVersion: '0.1.0', lockVersion: '0.0.9' });
|
|
121
126
|
expect(validateBunLockfileVersions(root)).toBe(1);
|
|
122
|
-
expect(syncBunLockfileVersions(root, { log: false })).toBe(1);
|
|
127
|
+
expect(syncBunLockfileVersions(root, { log: false, mode: 'install' })).toBe(1);
|
|
123
128
|
expect(lockVersionIn(root)).toBe('0.1.0');
|
|
124
129
|
});
|
|
125
130
|
|
|
126
131
|
it('stage: true stages the healed bun.lock', () => {
|
|
127
132
|
const root = makeFixtureRepo({
|
|
128
133
|
packageVersion: '0.1.3-next.0',
|
|
129
|
-
lockVersion: '0.1.
|
|
134
|
+
lockVersion: '0.1.2',
|
|
130
135
|
stableTag: 'foo@0.1.2',
|
|
131
136
|
});
|
|
132
|
-
expect(syncBunLockfileVersions(root, { log: false, stage: true })).toBe(1);
|
|
137
|
+
expect(syncBunLockfileVersions(root, { log: false, mode: 'install', stage: true })).toBe(1);
|
|
133
138
|
expect(git(root, 'diff --cached --name-only')).toContain('bun.lock');
|
|
134
139
|
});
|
|
135
140
|
|
|
136
|
-
it('
|
|
137
|
-
const root = makeFixtureRepo({ packageVersion: '0.1.2', lockVersion: '0.1.2', stableTag: 'foo@0.1.2' });
|
|
138
|
-
expect(syncBunLockfileVersions(root, { log: false, stage: true })).toBe(0);
|
|
139
|
-
expect(git(root, 'diff --cached --name-only').trim()).toBe('');
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
it('property: after sync, validation always passes', () => {
|
|
141
|
+
it('property: after install-mode sync, validation always passes', () => {
|
|
143
142
|
const version = fc
|
|
144
143
|
.tuple(fc.nat({ max: 20 }), fc.nat({ max: 20 }), fc.nat({ max: 20 }), fc.option(fc.nat({ max: 5 })))
|
|
145
144
|
.map(([major, minor, patch, next]) => `${major}.${minor}.${patch}${next === null ? '' : `-next.${next}`}`);
|
|
@@ -148,7 +147,7 @@ describe('bun.lock workspace version sync', () => {
|
|
|
148
147
|
fc.property(version, version, (packageVersion, lockVersion) => {
|
|
149
148
|
writeFixturePackage(root, packageVersion);
|
|
150
149
|
writeFixtureLock(root, lockVersion);
|
|
151
|
-
syncBunLockfileVersions(root, { log: false });
|
|
150
|
+
syncBunLockfileVersions(root, { log: false, mode: 'install' });
|
|
152
151
|
return validateBunLockfileVersions(root) === 0;
|
|
153
152
|
}),
|
|
154
153
|
{ numRuns: 25 },
|