@smoothbricks/cli 0.10.2 → 0.10.4
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 +31 -23
- 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 +36 -4
- package/dist/generate/index.d.ts.map +1 -1
- package/dist/generate/index.js +0 -6
- package/dist/github-ci/index.d.ts +17 -2
- package/dist/github-ci/index.d.ts.map +1 -1
- package/dist/github-ci/index.js +192 -26
- package/dist/github-ci/outputs.d.ts +22 -0
- package/dist/github-ci/outputs.d.ts.map +1 -0
- package/dist/github-ci/outputs.js +648 -0
- package/dist/lib/cli-package.js +2 -2
- package/dist/lib/json.d.ts +118 -11
- package/dist/lib/json.d.ts.map +1 -1
- package/dist/lib/json.js +317 -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 +28 -2
- package/dist/monorepo/git-config.js +2 -2
- package/dist/monorepo/index.d.ts +3 -1
- package/dist/monorepo/index.d.ts.map +1 -1
- package/dist/monorepo/index.js +6 -2
- package/dist/monorepo/lockfile.d.ts +13 -1
- package/dist/monorepo/lockfile.d.ts.map +1 -1
- package/dist/monorepo/lockfile.js +33 -21
- package/dist/monorepo/managed-files.d.ts +12 -0
- package/dist/monorepo/managed-files.d.ts.map +1 -1
- package/dist/monorepo/managed-files.js +86 -28
- package/dist/monorepo/package-policy.d.ts.map +1 -1
- package/dist/monorepo/package-policy.js +78 -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 +3 -0
- package/dist/monorepo/packs/index.d.ts.map +1 -1
- package/dist/monorepo/packs/index.js +18 -6
- package/dist/monorepo/publish-workflow.d.ts +1 -0
- package/dist/monorepo/publish-workflow.d.ts.map +1 -1
- package/dist/monorepo/publish-workflow.js +242 -3
- 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.map +1 -1
- package/dist/monorepo/tool-validation.js +32 -23
- package/dist/monorepo/wrangler.d.ts.map +1 -1
- package/dist/monorepo/wrangler.js +13 -13
- package/dist/nx/index.d.ts +12 -4
- package/dist/nx/index.d.ts.map +1 -1
- package/dist/nx/index.js +86 -19
- 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/github-release.d.ts +3 -0
- package/dist/release/github-release.d.ts.map +1 -1
- package/dist/release/github-release.js +11 -0
- package/dist/release/index.d.ts +20 -1
- package/dist/release/index.d.ts.map +1 -1
- package/dist/release/index.js +221 -89
- package/dist/release/orchestration.d.ts +8 -1
- package/dist/release/orchestration.d.ts.map +1 -1
- package/dist/release/orchestration.js +38 -1
- 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/git-format-staged.yml +19 -1
- package/managed/raw/tooling/direnv/github-actions-bootstrap.sh +6 -0
- package/managed/raw/tooling/direnv/merge-newer-pins.sh +3 -3
- package/managed/raw/tooling/direnv/setup-environment.ts +187 -19
- package/managed/raw/tooling/git-hooks/pre-commit.sh +0 -6
- package/managed/templates/github/actions/cache-nix-devenv/action.yml +12 -10
- package/managed/templates/github/actions/cache-node-modules/action.yml +6 -2
- package/managed/templates/github/actions/cache-nx/action.yml +2 -2
- package/managed/templates/github/actions/cache-ttsc-plugins/action.yml +19 -0
- package/managed/templates/github/actions/save-nix-devenv/action.yml +11 -3
- package/managed/templates/github/actions/setup-devenv/action.yml +5 -0
- package/managed/templates/github/workflows/managed-files.yml +80 -0
- package/package.json +18 -4
- package/src/bun/preload.ts +12 -0
- package/src/cli.ts +49 -11
- package/src/generate/index.ts +0 -6
- package/src/github-ci/index.test.ts +486 -0
- package/src/github-ci/index.ts +220 -30
- package/src/github-ci/outputs.ts +505 -0
- package/src/lib/cli-package.ts +2 -2
- package/src/lib/json.ts +291 -32
- package/src/lib/workspace.ts +88 -100
- package/src/monorepo/__tests__/ci-workflow.test.ts +14 -3
- package/src/monorepo/__tests__/publish-workflow.test.ts +222 -1
- package/src/monorepo/ci-workflow.ts +28 -2
- package/src/monorepo/git-config.ts +2 -2
- package/src/monorepo/index.ts +6 -2
- package/src/monorepo/lockfile.test.ts +35 -36
- package/src/monorepo/lockfile.ts +48 -24
- package/src/monorepo/managed-files.test.ts +63 -0
- package/src/monorepo/managed-files.ts +94 -34
- package/src/monorepo/package-policy.test.ts +70 -51
- package/src/monorepo/package-policy.ts +104 -59
- package/src/monorepo/packed-manifest.ts +18 -52
- package/src/monorepo/packed-package.ts +43 -22
- package/src/monorepo/packs/index.test.ts +18 -1
- package/src/monorepo/packs/index.ts +20 -7
- package/src/monorepo/publish-workflow.ts +482 -3
- package/src/monorepo/runtime.test.ts +42 -3
- package/src/monorepo/runtime.ts +90 -12
- package/src/monorepo/tool-validation.ts +35 -25
- package/src/monorepo/wrangler.test.ts +11 -15
- package/src/monorepo/wrangler.ts +15 -15
- package/src/nx/index.test.ts +43 -0
- package/src/nx/index.ts +110 -23
- package/src/pr/index.ts +42 -36
- package/src/release/__tests__/candidates.test.ts +5 -4
- package/src/release/__tests__/fixture-repo.test.ts +18 -16
- package/src/release/__tests__/github-release.test.ts +11 -0
- package/src/release/__tests__/orchestration.test.ts +57 -2
- package/src/release/__tests__/trust-publisher.test.ts +98 -2
- package/src/release/candidates.ts +10 -17
- package/src/release/github-release.ts +12 -0
- package/src/release/index.ts +321 -112
- package/src/release/orchestration.ts +55 -3
- package/src/wrangler/prepare-env.ts +75 -54
- package/src/wrangler/scaffold.test.ts +9 -12
- package/src/wrangler/scaffold.ts +13 -13
|
@@ -20,10 +20,78 @@ import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
|
20
20
|
import { join } from 'node:path';
|
|
21
21
|
import { $ } from 'bun';
|
|
22
22
|
import { getStaticTOMLValue, parseTOML } from 'toml-eslint-parser';
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
23
|
+
import typia from 'typia';
|
|
24
|
+
const isWranglerTomlRoot = (() => {
|
|
25
|
+
const _ip0 = input => undefined === input["binding"] || "string" === typeof input["binding"];
|
|
26
|
+
const _io0 = input => undefined === input.env || "object" === typeof input.env && null !== input.env && false === Array.isArray(input.env) && _io1(input.env);
|
|
27
|
+
const _io1 = input => Object.keys(input).every(key => {
|
|
28
|
+
const value = input[key];
|
|
29
|
+
if (undefined === value)
|
|
30
|
+
return true;
|
|
31
|
+
return undefined === value || "object" === typeof value && null !== value && false === Array.isArray(value) && _io2(value);
|
|
32
|
+
});
|
|
33
|
+
const _io2 = input => (undefined === input.vars || "object" === typeof input.vars && null !== input.vars && false === Array.isArray(input.vars) && _io3(input.vars)) && (undefined === input.kv_namespaces || Array.isArray(input.kv_namespaces) && input.kv_namespaces.every(elem => "object" === typeof elem && null !== elem && false === Array.isArray(elem) && _io4(elem))) && (undefined === input.d1_databases || Array.isArray(input.d1_databases) && input.d1_databases.every(elem => "object" === typeof elem && null !== elem && false === Array.isArray(elem) && _io5(elem))) && Object.keys(input).every(key => {
|
|
34
|
+
if (["vars", "kv_namespaces", "d1_databases"].some(prop => key === prop))
|
|
35
|
+
return true;
|
|
36
|
+
const value = input[key];
|
|
37
|
+
if (undefined === value)
|
|
38
|
+
return true;
|
|
39
|
+
return true;
|
|
40
|
+
});
|
|
41
|
+
const _io3 = input => Object.keys(input).every(key => {
|
|
42
|
+
const value = input[key];
|
|
43
|
+
if (undefined === value)
|
|
44
|
+
return true;
|
|
45
|
+
return true;
|
|
46
|
+
});
|
|
47
|
+
const _io4 = input => _ip0(input) && (undefined === input.id || "string" === typeof input.id);
|
|
48
|
+
const _io5 = input => _ip0(input) && (undefined === input.database_id || "string" === typeof input.database_id) && (undefined === input.database_name || "string" === typeof input.database_name);
|
|
49
|
+
return input => "object" === typeof input && null !== input && false === Array.isArray(input) && _io0(input);
|
|
50
|
+
})();
|
|
51
|
+
const isWranglerEnvBlock = (() => {
|
|
52
|
+
const _ip0 = input => undefined === input["binding"] || "string" === typeof input["binding"];
|
|
53
|
+
const _io0 = input => (undefined === input.vars || "object" === typeof input.vars && null !== input.vars && false === Array.isArray(input.vars) && _io1(input.vars)) && (undefined === input.kv_namespaces || Array.isArray(input.kv_namespaces) && input.kv_namespaces.every(elem => "object" === typeof elem && null !== elem && false === Array.isArray(elem) && _io2(elem))) && (undefined === input.d1_databases || Array.isArray(input.d1_databases) && input.d1_databases.every(elem => "object" === typeof elem && null !== elem && false === Array.isArray(elem) && _io3(elem))) && Object.keys(input).every(key => {
|
|
54
|
+
if (["vars", "kv_namespaces", "d1_databases"].some(prop => key === prop))
|
|
55
|
+
return true;
|
|
56
|
+
const value = input[key];
|
|
57
|
+
if (undefined === value)
|
|
58
|
+
return true;
|
|
59
|
+
return true;
|
|
60
|
+
});
|
|
61
|
+
const _io1 = input => Object.keys(input).every(key => {
|
|
62
|
+
const value = input[key];
|
|
63
|
+
if (undefined === value)
|
|
64
|
+
return true;
|
|
65
|
+
return true;
|
|
66
|
+
});
|
|
67
|
+
const _io2 = input => _ip0(input) && (undefined === input.id || "string" === typeof input.id);
|
|
68
|
+
const _io3 = input => _ip0(input) && (undefined === input.database_id || "string" === typeof input.database_id) && (undefined === input.database_name || "string" === typeof input.database_name);
|
|
69
|
+
return input => "object" === typeof input && null !== input && false === Array.isArray(input) && _io0(input);
|
|
70
|
+
})();
|
|
71
|
+
const isWranglerKvRow = (() => {
|
|
72
|
+
const _io0 = input => (undefined === input.binding || "string" === typeof input.binding) && (undefined === input.id || "string" === typeof input.id);
|
|
73
|
+
return input => "object" === typeof input && null !== input && false === Array.isArray(input) && _io0(input);
|
|
74
|
+
})();
|
|
75
|
+
const isWranglerD1Row = (() => {
|
|
76
|
+
const _io0 = input => (undefined === input.binding || "string" === typeof input.binding) && (undefined === input.database_id || "string" === typeof input.database_id) && (undefined === input.database_name || "string" === typeof input.database_name);
|
|
77
|
+
return input => "object" === typeof input && null !== input && false === Array.isArray(input) && _io0(input);
|
|
78
|
+
})();
|
|
79
|
+
const isWranglerSecretRow = (() => {
|
|
80
|
+
const _io0 = input => undefined === input.name || "string" === typeof input.name;
|
|
81
|
+
return input => "object" === typeof input && null !== input && false === Array.isArray(input) && _io0(input);
|
|
82
|
+
})();
|
|
83
|
+
const isKvNamespaceRow = (() => {
|
|
84
|
+
const _io0 = input => "string" === typeof input.id && "string" === typeof input.title;
|
|
85
|
+
return input => "object" === typeof input && null !== input && _io0(input);
|
|
86
|
+
})();
|
|
87
|
+
const isD1DatabaseRow = (() => {
|
|
88
|
+
const _io0 = input => "string" === typeof input.uuid && "string" === typeof input.name;
|
|
89
|
+
return input => "object" === typeof input && null !== input && _io0(input);
|
|
90
|
+
})();
|
|
91
|
+
const isShellErrorShape = (() => {
|
|
92
|
+
const _io0 = input => true && true;
|
|
93
|
+
return input => "object" === typeof input && null !== input && false === Array.isArray(input) && _io0(input);
|
|
94
|
+
})();
|
|
27
95
|
// ── wrangler.toml editors (CST: read via getStaticTOMLValue, write via
|
|
28
96
|
// node-range splice — comment/format-preserving by construction, no regex) ─────
|
|
29
97
|
/** All top-level tables (`[env.x]`, `[env.x.vars]`, `[[env.x.d1_databases]]`, …) — each carries `resolvedKey` (incl. array index). */
|
|
@@ -33,12 +101,11 @@ function tables(toml) {
|
|
|
33
101
|
/** Whole doc as a JS object (comments/format dropped — reads only). */
|
|
34
102
|
function tomlValue(toml) {
|
|
35
103
|
const value = getStaticTOMLValue(parseTOML(toml));
|
|
36
|
-
return
|
|
104
|
+
return isWranglerTomlRoot(value) ? value : {};
|
|
37
105
|
}
|
|
38
106
|
function envRecord(toml, env) {
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
return isRecord(block) ? block : null;
|
|
107
|
+
const block = tomlValue(toml).env?.[env];
|
|
108
|
+
return isWranglerEnvBlock(block) ? block : null;
|
|
42
109
|
}
|
|
43
110
|
function sameKey(a, b) {
|
|
44
111
|
return a.length === b.length && a.every((x, i) => x === b[i]);
|
|
@@ -54,8 +121,13 @@ function kvIn(toml, table, key) {
|
|
|
54
121
|
/** The `<field>` KV of the `[[env.<env>.<arrayKey>]]` table whose `binding` matches, or null. */
|
|
55
122
|
function arrayTableKv(toml, env, arrayKey, binding, field) {
|
|
56
123
|
const block = envRecord(toml, env);
|
|
57
|
-
const rows =
|
|
58
|
-
const index = rows.findIndex((
|
|
124
|
+
const rows = Array.isArray(block?.[arrayKey]) ? block[arrayKey] : [];
|
|
125
|
+
const index = rows.findIndex((row) => {
|
|
126
|
+
if (arrayKey === 'd1_databases') {
|
|
127
|
+
return isWranglerD1Row(row) && row.binding === binding;
|
|
128
|
+
}
|
|
129
|
+
return isWranglerKvRow(row) && row.binding === binding;
|
|
130
|
+
});
|
|
59
131
|
if (index < 0)
|
|
60
132
|
return null;
|
|
61
133
|
const table = tables(toml).find((t) => t.kind === 'array' && sameKey(t.resolvedKey, ['env', env, arrayKey, index]));
|
|
@@ -72,7 +144,7 @@ function endOfLine(toml, offset) {
|
|
|
72
144
|
/** First `[env.<name>]` (source order; also derived from a nested `[env.<name>.*]`), or null when no env declared. */
|
|
73
145
|
export function firstWranglerEnv(toml) {
|
|
74
146
|
const envs = tomlValue(toml).env;
|
|
75
|
-
if (!
|
|
147
|
+
if (!envs)
|
|
76
148
|
return null;
|
|
77
149
|
const [first] = Object.keys(envs);
|
|
78
150
|
return first ?? null;
|
|
@@ -80,13 +152,11 @@ export function firstWranglerEnv(toml) {
|
|
|
80
152
|
/** True if the toml declares `[env.<env>]` (or any `[env.<env>.*]` sub-table). */
|
|
81
153
|
export function hasEnvBlock(toml, env) {
|
|
82
154
|
const envs = tomlValue(toml).env;
|
|
83
|
-
return
|
|
155
|
+
return !!envs && env in envs;
|
|
84
156
|
}
|
|
85
157
|
/** A `[env.<env>.vars]` value, or null if unset/empty. Reads committed public config. */
|
|
86
158
|
export function getVar(toml, env, name) {
|
|
87
|
-
const
|
|
88
|
-
const vars = isRecord(block?.vars) ? block.vars : {};
|
|
89
|
-
const value = vars[name];
|
|
159
|
+
const value = envRecord(toml, env)?.vars?.[name];
|
|
90
160
|
return typeof value === 'string' && value ? value : null;
|
|
91
161
|
}
|
|
92
162
|
/** Set a `[env.<env>.vars]` value (JSON-encoded), preserving any trailing comment. Throws if the key is absent (scaffold first). */
|
|
@@ -99,10 +169,8 @@ export function setVar(toml, env, name, value) {
|
|
|
99
169
|
}
|
|
100
170
|
/** The id written for `[[env.<env>.kv_namespaces]]` binding, or null if absent/empty. */
|
|
101
171
|
export function getKvId(toml, env, binding) {
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
-
for (const row of rows) {
|
|
105
|
-
if (isRecord(row) && row.binding === binding && typeof row.id === 'string')
|
|
172
|
+
for (const row of envRecord(toml, env)?.kv_namespaces ?? []) {
|
|
173
|
+
if (row.binding === binding && typeof row.id === 'string')
|
|
106
174
|
return row.id || null;
|
|
107
175
|
}
|
|
108
176
|
return null;
|
|
@@ -148,20 +216,16 @@ export function blankKvIds(toml, env) {
|
|
|
148
216
|
}
|
|
149
217
|
/** The database_id written for `[[env.<env>.d1_databases]]` binding, or null if absent/empty. */
|
|
150
218
|
export function getD1Id(toml, env, binding) {
|
|
151
|
-
const
|
|
152
|
-
|
|
153
|
-
for (const row of rows) {
|
|
154
|
-
if (isRecord(row) && row.binding === binding && typeof row.database_id === 'string')
|
|
219
|
+
for (const row of envRecord(toml, env)?.d1_databases ?? []) {
|
|
220
|
+
if (row.binding === binding && typeof row.database_id === 'string')
|
|
155
221
|
return row.database_id || null;
|
|
156
222
|
}
|
|
157
223
|
return null;
|
|
158
224
|
}
|
|
159
225
|
/** The `database_name` for `[[env.<env>.d1_databases]]` binding, or null. Source of truth for which DB to create/reuse. */
|
|
160
226
|
export function getD1Name(toml, env, binding) {
|
|
161
|
-
const
|
|
162
|
-
|
|
163
|
-
for (const row of rows) {
|
|
164
|
-
if (isRecord(row) && row.binding === binding && typeof row.database_name === 'string')
|
|
227
|
+
for (const row of envRecord(toml, env)?.d1_databases ?? []) {
|
|
228
|
+
if (row.binding === binding && typeof row.database_name === 'string')
|
|
165
229
|
return row.database_name || null;
|
|
166
230
|
}
|
|
167
231
|
return null;
|
|
@@ -222,12 +286,9 @@ export function parseDevVarsExample(text) {
|
|
|
222
286
|
export function readManifest(root, env) {
|
|
223
287
|
const toml = readFileSync(join(root, 'wrangler.toml'), 'utf8');
|
|
224
288
|
const block = envRecord(toml, env);
|
|
225
|
-
const
|
|
226
|
-
const
|
|
227
|
-
const
|
|
228
|
-
const kvBindings = kvRows.flatMap((row) => (isRecord(row) && typeof row.binding === 'string' ? [row.binding] : []));
|
|
229
|
-
const d1Rows = isRecord(block) && Array.isArray(block.d1_databases) ? block.d1_databases : [];
|
|
230
|
-
const d1Bindings = d1Rows.flatMap((row) => (isRecord(row) && typeof row.binding === 'string' ? [row.binding] : []));
|
|
289
|
+
const vars = Object.keys(block?.vars ?? {});
|
|
290
|
+
const kvBindings = (block?.kv_namespaces ?? []).flatMap((row) => typeof row.binding === 'string' ? [row.binding] : []);
|
|
291
|
+
const d1Bindings = (block?.d1_databases ?? []).flatMap((row) => typeof row.binding === 'string' ? [row.binding] : []);
|
|
231
292
|
const examplePath = join(root, '.dev.vars.example');
|
|
232
293
|
const secrets = existsSync(examplePath) ? parseDevVarsExample(readFileSync(examplePath, 'utf8')) : [];
|
|
233
294
|
return { kvBindings, d1Bindings, vars, secrets };
|
|
@@ -252,25 +313,19 @@ export function looksLikeFileSecret(name) {
|
|
|
252
313
|
function asKvNamespaces(json) {
|
|
253
314
|
if (!Array.isArray(json))
|
|
254
315
|
return [];
|
|
255
|
-
|
|
256
|
-
for (const row of json) {
|
|
257
|
-
if (isRecord(row) && typeof row.id === 'string' && typeof row.title === 'string') {
|
|
258
|
-
out.push({ id: row.id, title: row.title });
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
return out;
|
|
316
|
+
return json.filter(isKvNamespaceRow);
|
|
262
317
|
}
|
|
263
318
|
function asSecretNames(json) {
|
|
264
319
|
if (!Array.isArray(json))
|
|
265
320
|
return [];
|
|
266
|
-
return json.flatMap((row) => (
|
|
321
|
+
return json.flatMap((row) => (isWranglerSecretRow(row) && typeof row.name === 'string' ? [row.name] : []));
|
|
267
322
|
}
|
|
268
323
|
/** Best-effort ERROR line from a Bun ShellError (thrown on non-zero exit). */
|
|
269
324
|
export function errText(err) {
|
|
270
|
-
if (!
|
|
325
|
+
if (!isShellErrorShape(err))
|
|
271
326
|
return 'unknown error';
|
|
272
|
-
const stderr =
|
|
273
|
-
const exit =
|
|
327
|
+
const stderr = err.stderr != null ? String(err.stderr) : '';
|
|
328
|
+
const exit = err.exitCode != null ? String(err.exitCode) : '?';
|
|
274
329
|
return stderr.split('\n').find((l) => l.includes('ERROR')) ?? `exit ${exit}`;
|
|
275
330
|
}
|
|
276
331
|
/** Live KV namespaces on the account (`kv namespace list` emits a pure JSON array); `[]` when offline/unauthenticated. */
|
|
@@ -305,13 +360,7 @@ async function reList(logical, cwd) {
|
|
|
305
360
|
function asD1Databases(json) {
|
|
306
361
|
if (!Array.isArray(json))
|
|
307
362
|
return [];
|
|
308
|
-
|
|
309
|
-
for (const row of json) {
|
|
310
|
-
if (isRecord(row) && typeof row.uuid === 'string' && typeof row.name === 'string') {
|
|
311
|
-
out.push({ uuid: row.uuid, name: row.name });
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
return out;
|
|
363
|
+
return json.filter(isD1DatabaseRow);
|
|
315
364
|
}
|
|
316
365
|
/** Live D1 databases on the account (`d1 list --json`); `[]` when offline/unauthenticated. */
|
|
317
366
|
export async function listD1Databases(cwd) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scaffold.d.ts","sourceRoot":"","sources":["../../src/wrangler/scaffold.ts"],"names":[],"mappings":"AAYA,MAAM,WAAW,eAAe;IAC9B,oDAAoD;IACpD,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;
|
|
1
|
+
{"version":3,"file":"scaffold.d.ts","sourceRoot":"","sources":["../../src/wrangler/scaffold.ts"],"names":[],"mappings":"AAYA,MAAM,WAAW,eAAe;IAC9B,oDAAoD;IACpD,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAqMD,4FAA4F;AAC5F,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,GAAE,eAAoB,GAAG,MAAM,CA8B7F"}
|
|
@@ -6,26 +6,25 @@
|
|
|
6
6
|
* .dev.vars.example), so it never drifts from what the worker declares.
|
|
7
7
|
*/
|
|
8
8
|
import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
9
|
-
import { join
|
|
10
|
-
import {
|
|
9
|
+
import { join } from 'node:path';
|
|
10
|
+
import { ensureNx, ensureNxTargets, writeJsonObject } from '../lib/json.js';
|
|
11
11
|
import { getWorkspacePackageManifests } from '../lib/workspace.js';
|
|
12
12
|
/** Locate a workspace package by nx name, package name, or relative path; must have a wrangler.toml. */
|
|
13
13
|
function resolveProject(root, project) {
|
|
14
14
|
const manifests = getWorkspacePackageManifests(root);
|
|
15
15
|
const match = manifests.find((m) => {
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
return nxName === project || m.name === project || relative(root, m.path) === project;
|
|
16
|
+
const nxName = m.json.nx?.name;
|
|
17
|
+
return nxName === project || m.name === project || m.path === project;
|
|
19
18
|
});
|
|
20
19
|
if (!match) {
|
|
21
|
-
const known = manifests.map((m) =>
|
|
20
|
+
const known = manifests.map((m) => m.json.nx?.name ?? m.name).join(', ');
|
|
22
21
|
throw new Error(`project "${project}" not found. Known: ${known}`);
|
|
23
22
|
}
|
|
24
|
-
const dir = match.path;
|
|
23
|
+
const dir = join(root, match.path);
|
|
25
24
|
if (!existsSync(join(dir, 'wrangler.toml'))) {
|
|
26
25
|
throw new Error(`"${project}" has no wrangler.toml — not a wrangler project`);
|
|
27
26
|
}
|
|
28
|
-
return { dir, label:
|
|
27
|
+
return { dir, label: match.path, packageJsonPath: match.packageJsonPath, json: match.json };
|
|
29
28
|
}
|
|
30
29
|
/** The generated starter script — generic, manifest-driven, prompt-batteries included. */
|
|
31
30
|
function renderScript(projectName) {
|
|
@@ -213,13 +212,14 @@ export function scaffold(root, project, options = {}) {
|
|
|
213
212
|
writeFileSync(scriptPath, renderScript(project));
|
|
214
213
|
console.log(`${existed ? 'overwrote' : 'created'} ${label}/scripts/prepare-env.ts`);
|
|
215
214
|
// Wire the nx target (idempotent).
|
|
216
|
-
const nx =
|
|
217
|
-
const targets =
|
|
218
|
-
if (!
|
|
219
|
-
|
|
215
|
+
const nx = ensureNx(json);
|
|
216
|
+
const targets = ensureNxTargets(nx);
|
|
217
|
+
if (!targets['prepare-env']) {
|
|
218
|
+
const prepareEnvTarget = {
|
|
220
219
|
executor: 'nx:run-commands',
|
|
221
220
|
options: { command: 'bun scripts/prepare-env.ts', cwd: '{projectRoot}' },
|
|
222
221
|
};
|
|
222
|
+
targets['prepare-env'] = prepareEnvTarget;
|
|
223
223
|
writeJsonObject(packageJsonPath, json);
|
|
224
224
|
console.log(`updated ${label}/package.json prepare-env target`);
|
|
225
225
|
}
|
|
@@ -8,12 +8,16 @@ formatters:
|
|
|
8
8
|
- '*.astro'
|
|
9
9
|
- '!.*' # Exclude hidden files
|
|
10
10
|
|
|
11
|
-
# Biome for JS/TS/
|
|
11
|
+
# Biome for JS/TS family (including CommonJS/CTS and ESM .mjs/.mts)
|
|
12
12
|
biome:
|
|
13
13
|
command: "biome check --files-ignore-unknown=true --use-editorconfig=true '--stdin-file-path={}' --fix"
|
|
14
14
|
patterns:
|
|
15
15
|
- '*.js'
|
|
16
|
+
- '*.cjs'
|
|
17
|
+
- '*.mjs'
|
|
16
18
|
- '*.ts'
|
|
19
|
+
- '*.cts'
|
|
20
|
+
- '*.mts'
|
|
17
21
|
- '*.jsx'
|
|
18
22
|
- '*.tsx'
|
|
19
23
|
- '*.json'
|
|
@@ -30,7 +34,11 @@ formatters:
|
|
|
30
34
|
- '*'
|
|
31
35
|
# Exclude files handled by Biome
|
|
32
36
|
- '!*.js'
|
|
37
|
+
- '!*.cjs'
|
|
38
|
+
- '!*.mjs'
|
|
33
39
|
- '!*.ts'
|
|
40
|
+
- '!*.cts'
|
|
41
|
+
- '!*.mts'
|
|
34
42
|
- '!*.jsx'
|
|
35
43
|
- '!*.tsx'
|
|
36
44
|
- '!*.json'
|
|
@@ -38,15 +46,25 @@ formatters:
|
|
|
38
46
|
- '!*.html'
|
|
39
47
|
- '!*.css'
|
|
40
48
|
- '!*.graphql'
|
|
49
|
+
# Exclude files handled by rustfmt
|
|
50
|
+
- '!*.rs'
|
|
41
51
|
# Exclude files handled by Alejandra
|
|
42
52
|
- '!*.nix'
|
|
43
53
|
# Bun lockfiles are generated and should not be rewritten by Prettier
|
|
44
54
|
- '!bun.lock'
|
|
55
|
+
# Cargo lockfiles are generated and should not be rewritten by Prettier
|
|
56
|
+
- '!Cargo.lock'
|
|
45
57
|
# Binary artifacts: Prettier reads them as UTF-8 and can corrupt them
|
|
46
58
|
# by replacing invalid byte sequences.
|
|
47
59
|
- '!*.wasm'
|
|
48
60
|
- '!*.wasm.sha256'
|
|
49
61
|
|
|
62
|
+
# rustfmt for Rust files
|
|
63
|
+
rustfmt:
|
|
64
|
+
command: 'rustfmt --edition 2024'
|
|
65
|
+
patterns:
|
|
66
|
+
- '*.rs'
|
|
67
|
+
|
|
50
68
|
# Alejandra for Nix files
|
|
51
69
|
alejandra:
|
|
52
70
|
command: 'alejandra'
|
|
@@ -46,6 +46,12 @@ install_devenv() {
|
|
|
46
46
|
|
|
47
47
|
build_devenv_shell() {
|
|
48
48
|
devenv shell --verbose -- date
|
|
49
|
+
# enterShell exports these for the bootstrap itself; persist them for the
|
|
50
|
+
# independent workflow step shells that run after this composite action.
|
|
51
|
+
if [ -n "${GITHUB_ENV:-}" ]; then
|
|
52
|
+
echo "TTSC_TSGO_BINARY=$repo_root/node_modules/@typescript/native/bin/tsc" >> "$GITHUB_ENV"
|
|
53
|
+
echo "TTSC_CACHE_DIR=$repo_root/.cache/ttsc" >> "$GITHUB_ENV"
|
|
54
|
+
fi
|
|
49
55
|
# Add repo-local tools only after the shell exists; cleanup steps use an
|
|
50
56
|
# explicit PATH because failures before this point must still refresh caches.
|
|
51
57
|
add_repo_paths
|
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
# Runtime version pins (nvfetcher overlay _sources/generated.{json,nix}) and the
|
|
5
5
|
# devenv/flake lock (devenv.lock) frequently differ across branches or across a
|
|
6
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
|
|
8
|
-
#
|
|
9
|
-
#
|
|
7
|
+
# higher pin wholesale so the tree always converges to the newest runtime.
|
|
8
|
+
# Repair package.json engine/packageManager pins explicitly with
|
|
9
|
+
# `smoo monorepo init --runtime-only` when needed.
|
|
10
10
|
#
|
|
11
11
|
# Wired by `smoo` (managed .gitattributes -> merge=smoo-newer-pins, and
|
|
12
12
|
# `git config merge.smoo-newer-pins.driver` installed by applyWorkspaceGitConfig).
|
|
@@ -1,12 +1,26 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
|
-
import { existsSync } from 'node:fs';
|
|
3
|
-
import { mkdir, rmdir } from 'node:fs/promises';
|
|
2
|
+
import { existsSync, mkdirSync, readdirSync, readlinkSync, rmSync, symlinkSync } from 'node:fs';
|
|
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
|
-
|
|
10
|
+
|
|
11
|
+
// A legitimate concurrent setup finishes well within this; anything older is a
|
|
12
|
+
// leftover from an interrupted run (CTRL-C/kill before the finally-cleanup) and
|
|
13
|
+
// would otherwise wedge every future shell load behind the 120s spin + EEXIST.
|
|
14
|
+
// NOTE: must be declared ABOVE the top-level setup block below — module consts
|
|
15
|
+
// are not hoisted, and the lock loop runs during that block.
|
|
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';
|
|
10
24
|
|
|
11
25
|
class CapturedCommandError extends Error {
|
|
12
26
|
constructor(
|
|
@@ -23,7 +37,9 @@ class CapturedCommandError extends Error {
|
|
|
23
37
|
process.chdir(projectRoot);
|
|
24
38
|
|
|
25
39
|
try {
|
|
26
|
-
//
|
|
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.
|
|
27
43
|
if (process.env.CI) {
|
|
28
44
|
try {
|
|
29
45
|
await runSetupCommand('bun install --frozen-lockfile', $`bun install --frozen-lockfile`, { quiet: false });
|
|
@@ -39,20 +55,9 @@ try {
|
|
|
39
55
|
await installLocalDependencies();
|
|
40
56
|
}
|
|
41
57
|
|
|
42
|
-
//
|
|
43
|
-
|
|
44
|
-
// even after bun install succeeds, so restart once into the real workspace.
|
|
45
|
-
if (startedWithoutNodeModules) {
|
|
46
|
-
await runSetupCommand('restart setup-environment.ts', $`bun --no-install ${import.meta.path}`, { quiet: false });
|
|
47
|
-
process.exit(0);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
if (!process.env.CI) {
|
|
51
|
-
const { syncRootRuntimeVersions } = await import('@smoothbricks/cli/monorepo/runtime');
|
|
52
|
-
await syncRootRuntimeVersions(projectRoot);
|
|
53
|
-
}
|
|
58
|
+
// Pin unscoped typescript → API 6 for root and Bun's shared .bun hoist (Nx).
|
|
59
|
+
ensureTypeScriptApiPackage(projectRoot);
|
|
54
60
|
|
|
55
|
-
const { applyWorkspaceGitConfig } = await import('@smoothbricks/cli/monorepo/git-config');
|
|
56
61
|
await applyWorkspaceGitConfig(projectRoot);
|
|
57
62
|
} catch (error) {
|
|
58
63
|
if (error instanceof CapturedCommandError) {
|
|
@@ -67,12 +72,147 @@ try {
|
|
|
67
72
|
}
|
|
68
73
|
|
|
69
74
|
async function installLocalDependencies(): Promise<void> {
|
|
70
|
-
//
|
|
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.
|
|
71
78
|
await withSetupLock(async () => {
|
|
72
79
|
await runSetupCommand('bun install --no-summary', $`bun install --no-summary`);
|
|
73
80
|
});
|
|
74
81
|
}
|
|
75
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
|
+
if (!existsSync(nativeBin)) {
|
|
102
|
+
throw new Error(`Missing ${nativeBin}. @typescript/native must remain the TypeScript 7 native compiler for ttsc.`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function findInstalledTypeScriptApiPackage(root: string): string | null {
|
|
107
|
+
const bunStore = path.join(root, 'node_modules', '.bun');
|
|
108
|
+
if (!existsSync(bunStore)) {
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
const exact = `typescript@${TYPESCRIPT_API_VERSION}`;
|
|
112
|
+
const names = readdirSync(bunStore)
|
|
113
|
+
.filter((name) => name === exact || name.startsWith(`${exact}+`) || name.startsWith('typescript@6.'))
|
|
114
|
+
.sort((a, b) => {
|
|
115
|
+
const aExact = a === exact || a.startsWith(`${exact}+`) ? 0 : 1;
|
|
116
|
+
const bExact = b === exact || b.startsWith(`${exact}+`) ? 0 : 1;
|
|
117
|
+
return aExact - bExact || a.localeCompare(b);
|
|
118
|
+
});
|
|
119
|
+
for (const name of names) {
|
|
120
|
+
const candidate = path.join(bunStore, name, 'node_modules', 'typescript');
|
|
121
|
+
if (existsSync(path.join(candidate, 'package.json'))) {
|
|
122
|
+
return candidate;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function forceSymlink(linkPath: string, targetPath: string): void {
|
|
129
|
+
mkdirSync(path.dirname(linkPath), { recursive: true });
|
|
130
|
+
const relativeTarget = path.relative(path.dirname(linkPath), targetPath);
|
|
131
|
+
let current: string | null = null;
|
|
132
|
+
try {
|
|
133
|
+
current = readlinkSync(linkPath);
|
|
134
|
+
} catch {
|
|
135
|
+
current = null;
|
|
136
|
+
}
|
|
137
|
+
if (
|
|
138
|
+
current === relativeTarget ||
|
|
139
|
+
(current !== null && path.resolve(path.dirname(linkPath), current) === targetPath)
|
|
140
|
+
) {
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
rmSync(linkPath, { recursive: true, force: true });
|
|
144
|
+
symlinkSync(relativeTarget, linkPath);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function assertTypescriptApiAt(typescriptRoot: string, expectedTarget: string): void {
|
|
148
|
+
if (!existsSync(typescriptRoot)) {
|
|
149
|
+
throw new Error(`Missing ${typescriptRoot} after linking TypeScript ${TYPESCRIPT_API_VERSION} API package`);
|
|
150
|
+
}
|
|
151
|
+
const requireFromInstall = createRequire(path.join(typescriptRoot, 'package.json'));
|
|
152
|
+
const typed = requireFromInstall(typescriptRoot) as { version?: string; readConfigFile?: unknown };
|
|
153
|
+
if (typeof typed.readConfigFile !== 'function' || typed.version !== TYPESCRIPT_API_VERSION) {
|
|
154
|
+
throw new Error(
|
|
155
|
+
`${typescriptRoot} must export TypeScript ${TYPESCRIPT_API_VERSION} compiler API (readConfigFile). ` +
|
|
156
|
+
`Resolved version ${typed.version ?? 'unknown'} (expected link target ${expectedTarget}). ` +
|
|
157
|
+
'Bun isolated linking often points .bun/node_modules/typescript at @typescript/native (TS7). ' +
|
|
158
|
+
'See https://github.com/oven-sh/bun/issues/33834.',
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Keep git hook wiring local to bootstrap. Runtime pin sync
|
|
165
|
+
* (`syncRootRuntimeVersions`) belongs to explicit monorepo tooling after the
|
|
166
|
+
* package graph exists — not the installer.
|
|
167
|
+
*/
|
|
168
|
+
async function applyWorkspaceGitConfig(root: string): Promise<void> {
|
|
169
|
+
const gitDirResult = await $`git rev-parse --git-dir`.cwd(root).quiet().nothrow();
|
|
170
|
+
if (gitDirResult.exitCode !== 0) {
|
|
171
|
+
throw new Error('Not in a git repository');
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const gitDir = path.resolve(root, new TextDecoder().decode(gitDirResult.stdout).trim());
|
|
175
|
+
const tooling = path.join(root, 'tooling');
|
|
176
|
+
|
|
177
|
+
await $`git config --local include.path ${path.join(tooling, 'workspace.gitconfig')}`.cwd(root);
|
|
178
|
+
|
|
179
|
+
// Keep the newer runtime version pins on any merge (nvfetcher overlay +
|
|
180
|
+
// devenv.lock) so a mirror sync's `git am --3way` never stalls on a version
|
|
181
|
+
// conflict. Mapped by the managed .gitattributes (merge=smoo-newer-pins);
|
|
182
|
+
// implemented in tooling/direnv/merge-newer-pins.sh.
|
|
183
|
+
await $`git config --local merge.smoo-newer-pins.name ${'keep the newer devenv/nvfetcher runtime pins'}`.cwd(root);
|
|
184
|
+
await $`git config --local merge.smoo-newer-pins.driver ${'bash tooling/direnv/merge-newer-pins.sh %O %A %B %P'}`.cwd(
|
|
185
|
+
root,
|
|
186
|
+
);
|
|
187
|
+
linkHook(gitDir, tooling, 'pre-commit');
|
|
188
|
+
linkHook(gitDir, tooling, 'commit-msg');
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function linkHook(gitDir: string, tooling: string, name: string): void {
|
|
192
|
+
const source = path.join(tooling, 'git-hooks', `${name}.sh`);
|
|
193
|
+
if (!existsSync(source)) {
|
|
194
|
+
throw new Error(`Missing ${name} hook source: ${source}`);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const target = path.join(gitDir, 'hooks', name);
|
|
198
|
+
if (readLinkOrNull(target) === source) {
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
console.log(`[!] Linking ${name} hook in ${gitDir}`);
|
|
203
|
+
mkdirSync(path.dirname(target), { recursive: true });
|
|
204
|
+
rmSync(target, { force: true });
|
|
205
|
+
symlinkSync(source, target);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function readLinkOrNull(hookPath: string): string | null {
|
|
209
|
+
try {
|
|
210
|
+
return readlinkSync(hookPath);
|
|
211
|
+
} catch {
|
|
212
|
+
return null;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
76
216
|
async function runSetupCommand(
|
|
77
217
|
command: string,
|
|
78
218
|
shell: ReturnType<typeof $>,
|
|
@@ -104,14 +244,42 @@ async function acquireLock(lockDir: string): Promise<void> {
|
|
|
104
244
|
await mkdir(lockDir, { recursive: false });
|
|
105
245
|
return;
|
|
106
246
|
} catch (error) {
|
|
107
|
-
if (!(error
|
|
247
|
+
if (!isFileExistsError(error) || Date.now() > deadline) {
|
|
108
248
|
throw error;
|
|
109
249
|
}
|
|
250
|
+
if (await isStaleLock(lockDir)) {
|
|
251
|
+
// Surface the self-heal so an interrupted-run leftover is visible in
|
|
252
|
+
// the direnv log rather than silently absorbed.
|
|
253
|
+
console.error(`! Breaking stale setup lock (${lockDir}) left by an interrupted run`);
|
|
254
|
+
// Best-effort: if a concurrent process breaks it first, the rmdir
|
|
255
|
+
// fails silently and the next mkdir attempt settles the race.
|
|
256
|
+
await rmdir(lockDir).catch(() => undefined);
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
110
259
|
await Bun.sleep(100);
|
|
111
260
|
}
|
|
112
261
|
}
|
|
113
262
|
}
|
|
114
263
|
|
|
264
|
+
async function isStaleLock(lockDir: string): Promise<boolean> {
|
|
265
|
+
try {
|
|
266
|
+
const info = await stat(lockDir);
|
|
267
|
+
return Date.now() - info.mtimeMs > STALE_LOCK_MS;
|
|
268
|
+
} catch (error) {
|
|
269
|
+
// Only "lock vanished" is expected here (a concurrent process released
|
|
270
|
+
// it); anything else must surface — a swallowed ReferenceError in this
|
|
271
|
+
// exact spot once disabled stale detection entirely.
|
|
272
|
+
if (error instanceof Error && 'code' in error && error.code === 'ENOENT') {
|
|
273
|
+
return false; // gone — let the mkdir retry acquire it
|
|
274
|
+
}
|
|
275
|
+
throw error;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function isFileExistsError(error: unknown): boolean {
|
|
280
|
+
return error instanceof Error && 'code' in error && error.code === 'EEXIST';
|
|
281
|
+
}
|
|
282
|
+
|
|
115
283
|
function replayCapturedOutput(error: unknown): void {
|
|
116
284
|
if (!(error instanceof CapturedCommandError)) {
|
|
117
285
|
return;
|
|
@@ -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
|