@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
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
|
-
import { existsSync } from 'node:fs';
|
|
2
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, readlinkSync, rmSync, symlinkSync } from 'node:fs';
|
|
3
3
|
import { mkdir, rmdir, stat } from 'node:fs/promises';
|
|
4
|
+
import { createRequire } from 'node:module';
|
|
4
5
|
import path from 'node:path';
|
|
5
6
|
import { $ } from 'bun';
|
|
6
7
|
|
|
7
8
|
const devenvRoot = process.env.DEVENV_ROOT;
|
|
8
9
|
const projectRoot = path.resolve(`${devenvRoot}/../..`);
|
|
9
|
-
const startedWithoutNodeModules = !existsSync(path.join(projectRoot, 'node_modules'));
|
|
10
10
|
|
|
11
11
|
// A legitimate concurrent setup finishes well within this; anything older is a
|
|
12
12
|
// leftover from an interrupted run (CTRL-C/kill before the finally-cleanup) and
|
|
@@ -14,6 +14,13 @@ const startedWithoutNodeModules = !existsSync(path.join(projectRoot, 'node_modul
|
|
|
14
14
|
// NOTE: must be declared ABOVE the top-level setup block below — module consts
|
|
15
15
|
// are not hoisted, and the lock loop runs during that block.
|
|
16
16
|
const STALE_LOCK_MS = 10 * 60_000;
|
|
17
|
+
// Unscoped require("typescript") must expose the TS6 compiler API for Nx.
|
|
18
|
+
// @typescript/native installs another package also named "typescript" (TS7). Bun's
|
|
19
|
+
// isolated linker often points node_modules/.bun/node_modules/typescript at TS7,
|
|
20
|
+
// so packages resolved from the .bun store (nx) get version.cjs without readConfigFile.
|
|
21
|
+
// After every install, force both unscoped typescript slots onto typescript@6.0.3.
|
|
22
|
+
// Keep @typescript/native for ttsc via TTSC_TSGO_BINARY. See https://github.com/oven-sh/bun/issues/33834.
|
|
23
|
+
const TYPESCRIPT_API_VERSION = '6.0.3';
|
|
17
24
|
|
|
18
25
|
class CapturedCommandError extends Error {
|
|
19
26
|
constructor(
|
|
@@ -30,7 +37,9 @@ class CapturedCommandError extends Error {
|
|
|
30
37
|
process.chdir(projectRoot);
|
|
31
38
|
|
|
32
39
|
try {
|
|
33
|
-
//
|
|
40
|
+
// Bootstrap only: install deps + wire local git hooks/config.
|
|
41
|
+
// Do not import workspace packages here — this script is what installs them,
|
|
42
|
+
// and package resolution/Typia transforms are not available yet.
|
|
34
43
|
if (process.env.CI) {
|
|
35
44
|
try {
|
|
36
45
|
await runSetupCommand('bun install --frozen-lockfile', $`bun install --frozen-lockfile`, { quiet: false });
|
|
@@ -46,20 +55,9 @@ try {
|
|
|
46
55
|
await installLocalDependencies();
|
|
47
56
|
}
|
|
48
57
|
|
|
49
|
-
//
|
|
50
|
-
|
|
51
|
-
// even after bun install succeeds, so restart once into the real workspace.
|
|
52
|
-
if (startedWithoutNodeModules) {
|
|
53
|
-
await runSetupCommand('restart setup-environment.ts', $`bun --no-install ${import.meta.path}`, { quiet: false });
|
|
54
|
-
process.exit(0);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
if (!process.env.CI) {
|
|
58
|
-
const { syncRootRuntimeVersions } = await import('@smoothbricks/cli/monorepo/runtime');
|
|
59
|
-
await syncRootRuntimeVersions(projectRoot);
|
|
60
|
-
}
|
|
58
|
+
// Pin unscoped typescript → API 6 for root and Bun's shared .bun hoist (Nx).
|
|
59
|
+
ensureTypeScriptApiPackage(projectRoot);
|
|
61
60
|
|
|
62
|
-
const { applyWorkspaceGitConfig } = await import('@smoothbricks/cli/monorepo/git-config');
|
|
63
61
|
await applyWorkspaceGitConfig(projectRoot);
|
|
64
62
|
} catch (error) {
|
|
65
63
|
if (error instanceof CapturedCommandError) {
|
|
@@ -74,14 +72,167 @@ try {
|
|
|
74
72
|
}
|
|
75
73
|
|
|
76
74
|
async function installLocalDependencies(): Promise<void> {
|
|
77
|
-
// bun install runs the root prepare script
|
|
78
|
-
//
|
|
79
|
-
//
|
|
75
|
+
// bun install runs the root prepare script. Multiple concurrent direnv
|
|
76
|
+
// activations can otherwise race while mutating the same files under
|
|
77
|
+
// node_modules.
|
|
80
78
|
await withSetupLock(async () => {
|
|
81
79
|
await runSetupCommand('bun install --no-summary', $`bun install --no-summary`);
|
|
82
80
|
});
|
|
83
81
|
}
|
|
84
82
|
|
|
83
|
+
function ensureTypeScriptApiPackage(root: string): void {
|
|
84
|
+
const apiPackageRoot = findInstalledTypeScriptApiPackage(root);
|
|
85
|
+
if (!apiPackageRoot) {
|
|
86
|
+
throw new Error(
|
|
87
|
+
`typescript@${TYPESCRIPT_API_VERSION} was not installed under node_modules/.bun. ` +
|
|
88
|
+
`Keep root devDependency typescript@^${TYPESCRIPT_API_VERSION} (compiler API for Nx).`,
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Root bare require("typescript") and Bun store-local requires (nx lives under
|
|
93
|
+
// node_modules/.bun/...) must both see the API package — not @typescript/native's TS7.
|
|
94
|
+
forceSymlink(path.join(root, 'node_modules', 'typescript'), apiPackageRoot);
|
|
95
|
+
forceSymlink(path.join(root, 'node_modules', '.bun', 'node_modules', 'typescript'), apiPackageRoot);
|
|
96
|
+
|
|
97
|
+
assertTypescriptApiAt(path.join(root, 'node_modules', 'typescript'), apiPackageRoot);
|
|
98
|
+
assertTypescriptApiAt(path.join(root, 'node_modules', '.bun', 'node_modules', 'typescript'), apiPackageRoot);
|
|
99
|
+
|
|
100
|
+
const nativeBin = path.join(root, 'node_modules', '@typescript', 'native', 'bin', 'tsc');
|
|
101
|
+
const rootPackageJson = path.join(root, 'package.json');
|
|
102
|
+
const declaresNative =
|
|
103
|
+
existsSync(rootPackageJson) &&
|
|
104
|
+
(() => {
|
|
105
|
+
try {
|
|
106
|
+
const pkg = JSON.parse(readFileSync(rootPackageJson, 'utf8')) as {
|
|
107
|
+
devDependencies?: Record<string, string>;
|
|
108
|
+
dependencies?: Record<string, string>;
|
|
109
|
+
};
|
|
110
|
+
return (
|
|
111
|
+
typeof pkg.devDependencies?.['@typescript/native'] === 'string' ||
|
|
112
|
+
typeof pkg.dependencies?.['@typescript/native'] === 'string'
|
|
113
|
+
);
|
|
114
|
+
} catch {
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
})();
|
|
118
|
+
if (declaresNative && !existsSync(nativeBin)) {
|
|
119
|
+
throw new Error(
|
|
120
|
+
`Missing ${nativeBin}. package.json declares @typescript/native but it is not installed. ` +
|
|
121
|
+
'Run bun install (or direnv reload). @typescript/native is the TypeScript 7 native compiler for ttsc.',
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function findInstalledTypeScriptApiPackage(root: string): string | null {
|
|
127
|
+
const bunStore = path.join(root, 'node_modules', '.bun');
|
|
128
|
+
if (!existsSync(bunStore)) {
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
const exact = `typescript@${TYPESCRIPT_API_VERSION}`;
|
|
132
|
+
const names = readdirSync(bunStore)
|
|
133
|
+
.filter((name) => name === exact || name.startsWith(`${exact}+`) || name.startsWith('typescript@6.'))
|
|
134
|
+
.sort((a, b) => {
|
|
135
|
+
const aExact = a === exact || a.startsWith(`${exact}+`) ? 0 : 1;
|
|
136
|
+
const bExact = b === exact || b.startsWith(`${exact}+`) ? 0 : 1;
|
|
137
|
+
return aExact - bExact || a.localeCompare(b);
|
|
138
|
+
});
|
|
139
|
+
for (const name of names) {
|
|
140
|
+
const candidate = path.join(bunStore, name, 'node_modules', 'typescript');
|
|
141
|
+
if (existsSync(path.join(candidate, 'package.json'))) {
|
|
142
|
+
return candidate;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function forceSymlink(linkPath: string, targetPath: string): void {
|
|
149
|
+
mkdirSync(path.dirname(linkPath), { recursive: true });
|
|
150
|
+
const relativeTarget = path.relative(path.dirname(linkPath), targetPath);
|
|
151
|
+
let current: string | null = null;
|
|
152
|
+
try {
|
|
153
|
+
current = readlinkSync(linkPath);
|
|
154
|
+
} catch {
|
|
155
|
+
current = null;
|
|
156
|
+
}
|
|
157
|
+
if (
|
|
158
|
+
current === relativeTarget ||
|
|
159
|
+
(current !== null && path.resolve(path.dirname(linkPath), current) === targetPath)
|
|
160
|
+
) {
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
rmSync(linkPath, { recursive: true, force: true });
|
|
164
|
+
symlinkSync(relativeTarget, linkPath);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function assertTypescriptApiAt(typescriptRoot: string, expectedTarget: string): void {
|
|
168
|
+
if (!existsSync(typescriptRoot)) {
|
|
169
|
+
throw new Error(`Missing ${typescriptRoot} after linking TypeScript ${TYPESCRIPT_API_VERSION} API package`);
|
|
170
|
+
}
|
|
171
|
+
const requireFromInstall = createRequire(path.join(typescriptRoot, 'package.json'));
|
|
172
|
+
const typed = requireFromInstall(typescriptRoot) as { version?: string; readConfigFile?: unknown };
|
|
173
|
+
if (typeof typed.readConfigFile !== 'function' || typed.version !== TYPESCRIPT_API_VERSION) {
|
|
174
|
+
throw new Error(
|
|
175
|
+
`${typescriptRoot} must export TypeScript ${TYPESCRIPT_API_VERSION} compiler API (readConfigFile). ` +
|
|
176
|
+
`Resolved version ${typed.version ?? 'unknown'} (expected link target ${expectedTarget}). ` +
|
|
177
|
+
'Bun isolated linking often points .bun/node_modules/typescript at @typescript/native (TS7). ' +
|
|
178
|
+
'See https://github.com/oven-sh/bun/issues/33834.',
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Keep git hook wiring local to bootstrap. Runtime pin sync
|
|
185
|
+
* (`syncRootRuntimeVersions`) belongs to explicit monorepo tooling after the
|
|
186
|
+
* package graph exists — not the installer.
|
|
187
|
+
*/
|
|
188
|
+
async function applyWorkspaceGitConfig(root: string): Promise<void> {
|
|
189
|
+
const gitDirResult = await $`git rev-parse --git-dir`.cwd(root).quiet().nothrow();
|
|
190
|
+
if (gitDirResult.exitCode !== 0) {
|
|
191
|
+
throw new Error('Not in a git repository');
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const gitDir = path.resolve(root, new TextDecoder().decode(gitDirResult.stdout).trim());
|
|
195
|
+
const tooling = path.join(root, 'tooling');
|
|
196
|
+
|
|
197
|
+
await $`git config --local include.path ${path.join(tooling, 'workspace.gitconfig')}`.cwd(root);
|
|
198
|
+
|
|
199
|
+
// Keep the newer runtime version pins on any merge (nvfetcher overlay +
|
|
200
|
+
// devenv.lock) so a mirror sync's `git am --3way` never stalls on a version
|
|
201
|
+
// conflict. Mapped by the managed .gitattributes (merge=smoo-newer-pins);
|
|
202
|
+
// implemented in tooling/direnv/merge-newer-pins.sh.
|
|
203
|
+
await $`git config --local merge.smoo-newer-pins.name ${'keep the newer devenv/nvfetcher runtime pins'}`.cwd(root);
|
|
204
|
+
await $`git config --local merge.smoo-newer-pins.driver ${'bash tooling/direnv/merge-newer-pins.sh %O %A %B %P'}`.cwd(
|
|
205
|
+
root,
|
|
206
|
+
);
|
|
207
|
+
linkHook(gitDir, tooling, 'pre-commit');
|
|
208
|
+
linkHook(gitDir, tooling, 'commit-msg');
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function linkHook(gitDir: string, tooling: string, name: string): void {
|
|
212
|
+
const source = path.join(tooling, 'git-hooks', `${name}.sh`);
|
|
213
|
+
if (!existsSync(source)) {
|
|
214
|
+
throw new Error(`Missing ${name} hook source: ${source}`);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const target = path.join(gitDir, 'hooks', name);
|
|
218
|
+
if (readLinkOrNull(target) === source) {
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
console.log(`[!] Linking ${name} hook in ${gitDir}`);
|
|
223
|
+
mkdirSync(path.dirname(target), { recursive: true });
|
|
224
|
+
rmSync(target, { force: true });
|
|
225
|
+
symlinkSync(source, target);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function readLinkOrNull(hookPath: string): string | null {
|
|
229
|
+
try {
|
|
230
|
+
return readlinkSync(hookPath);
|
|
231
|
+
} catch {
|
|
232
|
+
return null;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
85
236
|
async function runSetupCommand(
|
|
86
237
|
command: string,
|
|
87
238
|
shell: ReturnType<typeof $>,
|
|
@@ -9,12 +9,6 @@ set -e -o pipefail
|
|
|
9
9
|
# Format exactly what is staged so the commit includes the formatter output.
|
|
10
10
|
git-format-staged
|
|
11
11
|
|
|
12
|
-
# Any `bun install` rewrites bun.lock workspace versions from package.json,
|
|
13
|
-
# reverting the stable-tag versions the release flow syncs (see
|
|
14
|
-
# packages/cli/src/monorepo/lockfile.ts). Self-heal here instead of failing a
|
|
15
|
-
# later commit on drift it didn't cause.
|
|
16
|
-
smoo monorepo sync-bun-lockfile-versions --stage
|
|
17
|
-
|
|
18
12
|
# Adding a workspace package is rare and easy to leave half-wired. Run the
|
|
19
13
|
# full monorepo validator only for newly staged package manifests.
|
|
20
14
|
smoo monorepo validate --fail-fast --only-if-new-workspace-package
|
|
@@ -7,8 +7,11 @@ runs:
|
|
|
7
7
|
- name: Cache node_modules
|
|
8
8
|
uses: actions/cache@v5.0.5
|
|
9
9
|
with:
|
|
10
|
+
# Dependencies only. ttsc plugin/GOCACHE live under $TTSC_CACHE_DIR
|
|
11
|
+
# (.cache/ttsc), not inside node_modules.
|
|
12
|
+
# v2 isolates restore-keys from older Darwin caches with a broken TS dual-package hoist.
|
|
10
13
|
path: node_modules
|
|
11
14
|
# prettier-ignore
|
|
12
|
-
key: ${{ runner.os }}-${{ runner.arch }}-node-{{NODE_MODULES_CACHE_KEY}}
|
|
15
|
+
key: ${{ runner.os }}-${{ runner.arch }}-node-v2-{{NODE_MODULES_CACHE_KEY}}
|
|
13
16
|
restore-keys: |
|
|
14
|
-
${{ runner.os }}-${{ runner.arch }}-node-
|
|
17
|
+
${{ runner.os }}-${{ runner.arch }}-node-v2-
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
name: Cache ttsc plugins
|
|
2
|
+
description: >
|
|
3
|
+
Restore/save compiled ttsc native plugin binaries (typia, etc.). These are content-keyed under TTSC_CACHE_DIR/plugins
|
|
4
|
+
and skip the multi-minute `go build` when the cache key matches. The Go object cache (go-build/) is NOT cached — it is
|
|
5
|
+
multi-GB and only helps when the plugin key misses.
|
|
6
|
+
|
|
7
|
+
runs:
|
|
8
|
+
using: composite
|
|
9
|
+
steps:
|
|
10
|
+
- name: Cache ttsc plugin binaries
|
|
11
|
+
uses: actions/cache@v5.0.5
|
|
12
|
+
with:
|
|
13
|
+
# Only the compiled plugin binaries (~30MB each). Path is relative to the
|
|
14
|
+
# workspace; devenv sets TTSC_CACHE_DIR to <repo>/.cache/ttsc.
|
|
15
|
+
path: .cache/ttsc/plugins
|
|
16
|
+
# prettier-ignore
|
|
17
|
+
key: ${{ runner.os }}-${{ runner.arch }}-ttsc-plugins-v1-${{ hashFiles('bun.lock', 'package.json', 'patches/ttsc@*.patch', 'tooling/direnv/devenv.lock') }}
|
|
18
|
+
restore-keys: |
|
|
19
|
+
${{ runner.os }}-${{ runner.arch }}-ttsc-plugins-v1-
|
|
@@ -51,6 +51,11 @@ runs:
|
|
|
51
51
|
- name: 📦 Cache node_modules
|
|
52
52
|
uses: ./.github/actions/cache-node-modules
|
|
53
53
|
|
|
54
|
+
# Restores compiled ttsc native plugins into .cache/ttsc/plugins. TTSC_CACHE_DIR
|
|
55
|
+
# is set by devenv enterShell (+ bootstrap GITHUB_ENV for later steps).
|
|
56
|
+
- name: 🧊 Cache ttsc plugins
|
|
57
|
+
uses: ./.github/actions/cache-ttsc-plugins
|
|
58
|
+
|
|
54
59
|
- name: 🐚 Build devenv shell
|
|
55
60
|
shell: bash
|
|
56
61
|
working-directory: tooling/direnv
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# Managed by @smoothbricks/cli (smoo monorepo update) - do not edit by hand.
|
|
2
|
+
#
|
|
3
|
+
# Self-healing loop for smoo-managed files: ci.yml runs the authoritative
|
|
4
|
+
# `smoo monorepo check` on every default-branch push (devenv is already up
|
|
5
|
+
# there, so the check is ~free) and dispatches THIS workflow only when real
|
|
6
|
+
# drift exists - typically after a @smoothbricks/cli pin bump. It runs
|
|
7
|
+
# `smoo monorepo update` and pushes the result to ONE persistent review branch
|
|
8
|
+
# (chore/managed-files) with ONE PR. The PR is NEVER auto-merged: a template
|
|
9
|
+
# change is upstream code that deserves a human look before it lands. Re-runs
|
|
10
|
+
# refresh the same branch in place.
|
|
11
|
+
#
|
|
12
|
+
# NOTE: managed files include .github/workflows/*, and the default GITHUB_TOKEN
|
|
13
|
+
# is server-side forbidden to push workflow-file changes. Configure a GitHub App
|
|
14
|
+
# or PAT token with contents:write + workflows:write as MANAGED_FILES_TOKEN;
|
|
15
|
+
# without it, runs that touch workflow files fail at push with a clear error.
|
|
16
|
+
name: Managed files
|
|
17
|
+
|
|
18
|
+
on:
|
|
19
|
+
workflow_dispatch:
|
|
20
|
+
|
|
21
|
+
permissions:
|
|
22
|
+
contents: write
|
|
23
|
+
pull-requests: write
|
|
24
|
+
|
|
25
|
+
concurrency:
|
|
26
|
+
group: managed-files
|
|
27
|
+
cancel-in-progress: true
|
|
28
|
+
|
|
29
|
+
defaults:
|
|
30
|
+
run:
|
|
31
|
+
working-directory: tooling/direnv
|
|
32
|
+
|
|
33
|
+
jobs:
|
|
34
|
+
update:
|
|
35
|
+
runs-on: ubuntu-latest
|
|
36
|
+
timeout-minutes: 30
|
|
37
|
+
steps:
|
|
38
|
+
- name: 📥 Checkout
|
|
39
|
+
uses: actions/checkout@v6.0.2
|
|
40
|
+
with:
|
|
41
|
+
filter: blob:none
|
|
42
|
+
fetch-depth: 0
|
|
43
|
+
persist-credentials: true
|
|
44
|
+
token: ${{ secrets.MANAGED_FILES_TOKEN || github.token }}
|
|
45
|
+
|
|
46
|
+
- name: 🧱 Setup Nix/devenv
|
|
47
|
+
id: setup
|
|
48
|
+
uses: ./.github/actions/setup-devenv
|
|
49
|
+
|
|
50
|
+
- name: 🔄 Regenerate managed files
|
|
51
|
+
run: smoo monorepo update
|
|
52
|
+
|
|
53
|
+
- name: 📤 Push review branch and open PR
|
|
54
|
+
env:
|
|
55
|
+
GH_TOKEN: ${{ secrets.MANAGED_FILES_TOKEN || github.token }}
|
|
56
|
+
run: |
|
|
57
|
+
cd "$(git rev-parse --show-toplevel)"
|
|
58
|
+
if [ -z "$(git status --porcelain)" ]; then
|
|
59
|
+
echo "Managed files are in sync; nothing to do."
|
|
60
|
+
exit 0
|
|
61
|
+
fi
|
|
62
|
+
git config user.name "github-actions[bot]"
|
|
63
|
+
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
|
64
|
+
git switch -C chore/managed-files
|
|
65
|
+
git add -A
|
|
66
|
+
git commit -m "chore: regenerate smoo-managed files"
|
|
67
|
+
git push --force origin chore/managed-files
|
|
68
|
+
gh pr create \
|
|
69
|
+
--head chore/managed-files \
|
|
70
|
+
--base "${GITHUB_REF_NAME}" \
|
|
71
|
+
--title "chore: regenerate smoo-managed files" \
|
|
72
|
+
--body "Automated refresh of smoo-managed files after drift (usually a @smoothbricks/cli pin bump). One persistent PR; re-runs force-push this branch in place. Review the template changes - do NOT enable auto-merge." \
|
|
73
|
+
2>/dev/null || echo "PR already open; branch refreshed in place."
|
|
74
|
+
|
|
75
|
+
- name: 🧹 Cleanup and cache Nix/devenv
|
|
76
|
+
if: ${{ always() }}
|
|
77
|
+
uses: ./.github/actions/save-nix-devenv
|
|
78
|
+
with:
|
|
79
|
+
nix-cache-hit: ${{ steps.setup.outputs.nix-cache-hit }}
|
|
80
|
+
devenv-cache-hit: ${{ steps.setup.outputs.devenv-cache-hit }}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@smoothbricks/cli",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.5",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "SmoothBricks monorepo automation CLI",
|
|
6
6
|
"bin": {
|
|
@@ -20,6 +20,13 @@
|
|
|
20
20
|
"import": "./dist/index.js",
|
|
21
21
|
"default": "./dist/index.js"
|
|
22
22
|
},
|
|
23
|
+
"./bun/preload": {
|
|
24
|
+
"types": "./dist/bun/preload.d.ts",
|
|
25
|
+
"bun": "./src/bun/preload.ts",
|
|
26
|
+
"development": "./src/bun/preload.ts",
|
|
27
|
+
"import": "./dist/bun/preload.js",
|
|
28
|
+
"default": "./dist/bun/preload.js"
|
|
29
|
+
},
|
|
23
30
|
"./monorepo/runtime": {
|
|
24
31
|
"types": "./dist/monorepo/runtime.d.ts",
|
|
25
32
|
"bun": "./src/monorepo/runtime.ts",
|
|
@@ -42,6 +49,10 @@
|
|
|
42
49
|
"default": "./dist/wrangler/prepare-env.js"
|
|
43
50
|
}
|
|
44
51
|
},
|
|
52
|
+
"sideEffects": [
|
|
53
|
+
"./dist/bun/preload.js",
|
|
54
|
+
"./src/bun/preload.ts"
|
|
55
|
+
],
|
|
45
56
|
"files": [
|
|
46
57
|
"README.md",
|
|
47
58
|
"bin",
|
|
@@ -53,8 +64,8 @@
|
|
|
53
64
|
"dependencies": {
|
|
54
65
|
"@arethetypeswrong/core": "^0.18.2",
|
|
55
66
|
"@prettier/sync": "^0.6.1",
|
|
56
|
-
"@smoothbricks/nx-plugin": "0.3.
|
|
57
|
-
"@smoothbricks/validation": "0.1.
|
|
67
|
+
"@smoothbricks/nx-plugin": "0.3.4",
|
|
68
|
+
"@smoothbricks/validation": "0.1.4",
|
|
58
69
|
"commander": "^14.0.3",
|
|
59
70
|
"prettier": "^3.6.1",
|
|
60
71
|
"publint": "^0.3.18",
|
|
@@ -62,7 +73,7 @@
|
|
|
62
73
|
"sherif": "^1.11.1",
|
|
63
74
|
"toml-eslint-parser": "^1.0.3",
|
|
64
75
|
"tslib": "^2.8.1",
|
|
65
|
-
"typia": "13.1.
|
|
76
|
+
"typia": "13.1.19"
|
|
66
77
|
},
|
|
67
78
|
"devDependencies": {
|
|
68
79
|
"fast-check": "^4.4.0"
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bun runtime preload for consumers of `@smoothbricks/cli`.
|
|
3
|
+
*
|
|
4
|
+
* Re-exports the Typia/ttsc transform preload from `@smoothbricks/validation`.
|
|
5
|
+
* External repos depend on the CLI package only — they must not need a direct
|
|
6
|
+
* `@smoothbricks/validation` dependency just to run `smoo` against source.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* import '@smoothbricks/cli/bun/preload';
|
|
10
|
+
* // or bunfig.toml: preload = ["@smoothbricks/cli/bun/preload"]
|
|
11
|
+
*/
|
|
12
|
+
import '@smoothbricks/validation/bun/preload';
|
package/src/cli.ts
CHANGED
|
@@ -59,12 +59,15 @@ function buildProgram(): Command {
|
|
|
59
59
|
);
|
|
60
60
|
monorepo.command('update').action(async () => {
|
|
61
61
|
const { updateManagedFiles } = await import('./monorepo/index.js');
|
|
62
|
-
updateManagedFiles(await findRepoRoot());
|
|
63
|
-
});
|
|
64
|
-
monorepo.command('check').action(async () => {
|
|
65
|
-
const { checkManagedFiles } = await import('./monorepo/index.js');
|
|
66
|
-
checkManagedFiles(await findRepoRoot());
|
|
62
|
+
await updateManagedFiles(await findRepoRoot());
|
|
67
63
|
});
|
|
64
|
+
monorepo
|
|
65
|
+
.command('check')
|
|
66
|
+
.option('--warn', 'report drift as warnings (GitHub annotations) instead of failing')
|
|
67
|
+
.action(async (options: { warn?: boolean }) => {
|
|
68
|
+
const { checkManagedFiles } = await import('./monorepo/index.js');
|
|
69
|
+
checkManagedFiles(await findRepoRoot(), { warn: options.warn });
|
|
70
|
+
});
|
|
68
71
|
monorepo.command('diff').action(async () => {
|
|
69
72
|
const { diffManagedFiles } = await import('./monorepo/index.js');
|
|
70
73
|
diffManagedFiles(await findRepoRoot());
|
|
@@ -78,10 +81,19 @@ function buildProgram(): Command {
|
|
|
78
81
|
});
|
|
79
82
|
monorepo
|
|
80
83
|
.command('sync-bun-lockfile-versions')
|
|
81
|
-
.option('--stage', 'stage bun.lock when versions were resynced
|
|
82
|
-
.
|
|
84
|
+
.option('--stage', 'stage bun.lock when versions were resynced; quiet when clean')
|
|
85
|
+
.option(
|
|
86
|
+
'--mode <mode>',
|
|
87
|
+
'install: match package.json (default, CI); publish: map unpublished -next to last stable tag (pre-pack only)',
|
|
88
|
+
'install',
|
|
89
|
+
)
|
|
90
|
+
.action(async (options: { stage?: boolean; mode?: 'install' | 'publish' }) => {
|
|
83
91
|
const { syncBunLockfileVersions } = await import('./monorepo/index.js');
|
|
84
|
-
|
|
92
|
+
const mode = options.mode === 'publish' ? 'publish' : 'install';
|
|
93
|
+
syncBunLockfileVersions(await findRepoRoot(), {
|
|
94
|
+
mode,
|
|
95
|
+
...(options.stage ? { log: false, stage: true } : {}),
|
|
96
|
+
});
|
|
85
97
|
});
|
|
86
98
|
monorepo
|
|
87
99
|
.command('list-release-packages')
|
package/src/generate/index.ts
CHANGED
|
@@ -42,12 +42,6 @@ export const variants: Record<string, GenerateVariant> = {
|
|
|
42
42
|
args: (name) => ['--name', name, '--variant', 'ts-lib'],
|
|
43
43
|
options: [{ flag: '--public', description: 'configure for npm publication' }],
|
|
44
44
|
},
|
|
45
|
-
'ts-zig': {
|
|
46
|
-
generator: 'create-package',
|
|
47
|
-
description: 'Create a TypeScript + Zig/WASM hybrid package',
|
|
48
|
-
args: (name) => ['--name', name, '--variant', 'ts-zig'],
|
|
49
|
-
options: [{ flag: '--public', description: 'configure for npm publication' }],
|
|
50
|
-
},
|
|
51
45
|
'make-public': {
|
|
52
46
|
generator: 'make-public',
|
|
53
47
|
description: 'Promote a private package to npm publication',
|
package/src/github-ci/index.ts
CHANGED
|
@@ -1,11 +1,30 @@
|
|
|
1
|
-
import { existsSync } from 'node:fs';
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
2
|
import { appendFile, mkdtemp, realpath, rename, rm } from 'node:fs/promises';
|
|
3
3
|
import { dirname, join } from 'node:path';
|
|
4
4
|
import { $ } from 'bun';
|
|
5
|
+
import typia from 'typia';
|
|
6
|
+
import { parseStringArrayText } from '../lib/json.js';
|
|
5
7
|
import { decode, run, runStatus } from '../lib/run.js';
|
|
6
8
|
import { type ProjectTargets, readProjectTargets } from '../nx/index.js';
|
|
7
9
|
import type { NxTargetRun } from './outputs.js';
|
|
8
10
|
|
|
11
|
+
interface GithubActionsEventPayload {
|
|
12
|
+
repository?: {
|
|
13
|
+
default_branch?: string;
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface NxProjectDeployConfigurations {
|
|
18
|
+
targets?: {
|
|
19
|
+
deploy?: {
|
|
20
|
+
configurations?: Record<string, unknown>;
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const parseGithubActionsEvent = typia.json.createIsParse<GithubActionsEventPayload>();
|
|
26
|
+
const parseNxProjectDeployConfigurations = typia.json.createIsParse<NxProjectDeployConfigurations>();
|
|
27
|
+
|
|
9
28
|
type NxSmartMode = 'auto' | 'affected' | 'run-many';
|
|
10
29
|
|
|
11
30
|
export async function cleanupGithubCiCache(root: string): Promise<void> {
|
|
@@ -279,7 +298,7 @@ export async function githubCiApplyOutputs(
|
|
|
279
298
|
async function loadOutputBoundary() {
|
|
280
299
|
if (import.meta.url.endsWith('/src/github-ci/index.ts')) {
|
|
281
300
|
// The source self-hosting shim has no Typia transform; register it before loading manifest validators.
|
|
282
|
-
await import('@smoothbricks/
|
|
301
|
+
await import('@smoothbricks/cli/bun/preload');
|
|
283
302
|
}
|
|
284
303
|
// This boundary must stay lazy because the transformed dist and source self-hosting paths initialize Typia differently.
|
|
285
304
|
return import('./outputs.js');
|
|
@@ -341,7 +360,31 @@ function resolveNxSmartMode(mode: NxSmartMode): 'affected' | 'run-many' {
|
|
|
341
360
|
if (mode === 'affected' || mode === 'run-many') {
|
|
342
361
|
return mode;
|
|
343
362
|
}
|
|
344
|
-
|
|
363
|
+
const defaultBranch = eventDefaultBranch() ?? 'main';
|
|
364
|
+
if (process.env.GITHUB_EVENT_NAME === 'push') {
|
|
365
|
+
return process.env.GITHUB_REF_NAME === defaultBranch ? 'run-many' : 'affected';
|
|
366
|
+
}
|
|
367
|
+
// A PR into a NON-default branch is an integration surface (e.g. a mirror-sync
|
|
368
|
+
// review branch): its base moves outside the default-branch workflow that
|
|
369
|
+
// affected scoping is calibrated against, so under-selection can pass PR CI
|
|
370
|
+
// and only fail after merge. Validate those PRs in full.
|
|
371
|
+
if (process.env.GITHUB_EVENT_NAME === 'pull_request') {
|
|
372
|
+
const base = process.env.GITHUB_BASE_REF;
|
|
373
|
+
return base && base !== defaultBranch ? 'run-many' : 'affected';
|
|
374
|
+
}
|
|
375
|
+
return 'affected';
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/** The repository default branch from the Actions event payload. */
|
|
379
|
+
function eventDefaultBranch(): string | undefined {
|
|
380
|
+
const eventPath = process.env.GITHUB_EVENT_PATH;
|
|
381
|
+
if (!eventPath) return undefined;
|
|
382
|
+
try {
|
|
383
|
+
const payload = parseGithubActionsEvent(readFileSync(eventPath, 'utf8'));
|
|
384
|
+
return payload?.repository?.default_branch || undefined;
|
|
385
|
+
} catch {
|
|
386
|
+
return undefined;
|
|
387
|
+
}
|
|
345
388
|
}
|
|
346
389
|
|
|
347
390
|
async function deployProjectsWithConfiguration(
|
|
@@ -366,24 +409,12 @@ async function deployProjectsWithConfiguration(
|
|
|
366
409
|
|
|
367
410
|
async function deployTargetHasConfiguration(root: string, project: string, configuration: string): Promise<boolean> {
|
|
368
411
|
const result = await $`nx show project ${project} --json`.cwd(root).quiet();
|
|
369
|
-
const parsed
|
|
370
|
-
|
|
371
|
-
const deploy = recordValue(targets)?.deploy;
|
|
372
|
-
const configurations = recordValue(deploy)?.configurations;
|
|
373
|
-
return recordValue(configurations)?.[configuration] !== undefined;
|
|
412
|
+
const parsed = parseNxProjectDeployConfigurations(decode(result.stdout));
|
|
413
|
+
return parsed?.targets?.deploy?.configurations?.[configuration] !== undefined;
|
|
374
414
|
}
|
|
375
415
|
|
|
376
416
|
function nxProjectList(output: string): string[] {
|
|
377
|
-
|
|
378
|
-
return Array.isArray(parsed) ? parsed.filter((project): project is string => typeof project === 'string') : [];
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
function recordValue(value: unknown): Record<string, unknown> | undefined {
|
|
382
|
-
return isRecord(value) ? value : undefined;
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
386
|
-
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
417
|
+
return parseStringArrayText(output) ?? [];
|
|
387
418
|
}
|
|
388
419
|
|
|
389
420
|
async function createGithubStatus(name: string, step: string): Promise<void> {
|
package/src/github-ci/outputs.ts
CHANGED
|
@@ -2,7 +2,6 @@ import { createHash } from 'node:crypto';
|
|
|
2
2
|
import { createReadStream, type Dirent, type Stats } from 'node:fs';
|
|
3
3
|
import { copyFile, lstat, mkdir, readdir, readFile, writeFile } from 'node:fs/promises';
|
|
4
4
|
import { isAbsolute, relative, resolve, sep } from 'node:path';
|
|
5
|
-
import { isRecord } from '@smoothbricks/validation';
|
|
6
5
|
import typia from 'typia';
|
|
7
6
|
import { type ProjectTargets, readProjectTargets } from '../nx/index.js';
|
|
8
7
|
|
|
@@ -492,7 +491,7 @@ function assertGitSha(value: string, description: string): void {
|
|
|
492
491
|
}
|
|
493
492
|
|
|
494
493
|
function isMissingPathError(error: unknown): boolean {
|
|
495
|
-
return
|
|
494
|
+
return error instanceof Error && 'code' in error && error.code === 'ENOENT';
|
|
496
495
|
}
|
|
497
496
|
|
|
498
497
|
function sha256File(path: string): Promise<string> {
|
package/src/lib/cli-package.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { fileURLToPath } from 'node:url';
|
|
2
|
-
import { readJsonObject
|
|
2
|
+
import { readJsonObject } from './json.js';
|
|
3
3
|
|
|
4
4
|
export const smoothBricksCodebasePackageName = '@smoothbricks/codebase';
|
|
5
5
|
export const cliPackageVersion = readCliPackageVersion();
|
|
6
6
|
|
|
7
7
|
function readCliPackageVersion(): string {
|
|
8
8
|
const pkg = readJsonObject(fileURLToPath(new URL('../../package.json', import.meta.url)));
|
|
9
|
-
const version = pkg
|
|
9
|
+
const version = pkg?.version;
|
|
10
10
|
if (!version) {
|
|
11
11
|
throw new Error('Unable to read @smoothbricks/cli package version.');
|
|
12
12
|
}
|