@smoothbricks/cli 0.10.6 → 0.10.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +24 -1
- package/dist/github-ci/index.d.ts +44 -3
- package/dist/github-ci/index.d.ts.map +1 -1
- package/dist/github-ci/index.js +242 -36
- package/dist/lib/json.d.ts +1 -1
- package/dist/lib/json.d.ts.map +1 -1
- package/dist/lib/json.js +3 -18
- package/dist/lib/workspace.d.ts.map +1 -1
- package/dist/lib/workspace.js +1 -0
- package/dist/monorepo/ci-workflow.js +16 -6
- package/dist/monorepo/managed-files.d.ts +6 -22
- package/dist/monorepo/managed-files.d.ts.map +1 -1
- package/dist/monorepo/managed-files.js +31 -48
- package/dist/monorepo/package-policy.js +2 -2
- package/dist/monorepo/packs/index.d.ts.map +1 -1
- package/dist/monorepo/packs/index.js +2 -0
- package/dist/monorepo/pr-preview-cleanup-workflow.d.ts +5 -0
- package/dist/monorepo/pr-preview-cleanup-workflow.d.ts.map +1 -0
- package/dist/monorepo/pr-preview-cleanup-workflow.js +38 -0
- package/dist/monorepo/publish-workflow.js +3 -3
- package/dist/monorepo/runtime.d.ts +2 -2
- package/dist/monorepo/runtime.d.ts.map +1 -1
- package/dist/monorepo/runtime.js +11 -16
- package/dist/monorepo/tool-validation.d.ts.map +1 -1
- package/dist/monorepo/tool-validation.js +105 -28
- package/dist/nx/index.d.ts +7 -4
- package/dist/nx/index.d.ts.map +1 -1
- package/dist/nx/index.js +117 -56
- package/dist/playwright/index.d.ts +22 -0
- package/dist/playwright/index.d.ts.map +1 -0
- package/dist/playwright/index.js +44 -0
- package/dist/release/bootstrap-npm-packages.d.ts +3 -0
- package/dist/release/bootstrap-npm-packages.d.ts.map +1 -1
- package/dist/release/bootstrap-npm-packages.js +21 -0
- package/dist/release/index.d.ts.map +1 -1
- package/dist/release/index.js +17 -0
- package/dist/wrangler/cloudflare.d.ts +85 -0
- package/dist/wrangler/cloudflare.d.ts.map +1 -0
- package/dist/wrangler/cloudflare.js +235 -0
- package/dist/wrangler/deploy-environment.d.ts +48 -0
- package/dist/wrangler/deploy-environment.d.ts.map +1 -0
- package/dist/wrangler/deploy-environment.js +383 -0
- package/dist/wrangler/environment.d.ts +58 -0
- package/dist/wrangler/environment.d.ts.map +1 -0
- package/dist/wrangler/environment.js +297 -0
- package/managed/raw/tooling/devenv +71 -0
- package/managed/raw/tooling/direnv/github-actions-bootstrap.sh +3 -4
- package/package.json +11 -4
- package/src/cli.ts +33 -3
- package/src/github-ci/index.test.ts +221 -1
- package/src/github-ci/index.ts +303 -32
- package/src/lib/json.ts +1 -1
- package/src/lib/workspace.ts +1 -0
- package/src/monorepo/__tests__/ci-workflow.test.ts +10 -4
- package/src/monorepo/__tests__/pr-preview-cleanup-workflow.test.ts +23 -0
- package/src/monorepo/__tests__/publish-workflow.test.ts +12 -4
- package/src/monorepo/ci-workflow.ts +16 -6
- package/src/monorepo/managed-files.test.ts +87 -23
- package/src/monorepo/managed-files.ts +35 -69
- package/src/monorepo/package-policy.test.ts +31 -0
- package/src/monorepo/package-policy.ts +2 -2
- package/src/monorepo/packs/index.ts +2 -0
- package/src/monorepo/pr-preview-cleanup-workflow.ts +44 -0
- package/src/monorepo/publish-workflow.ts +7 -2
- package/src/monorepo/runtime.test.ts +23 -17
- package/src/monorepo/runtime.ts +12 -17
- package/src/monorepo/tool-validation.test.ts +100 -8
- package/src/monorepo/tool-validation.ts +115 -31
- package/src/nx/index.test.ts +45 -14
- package/src/nx/index.ts +133 -64
- package/src/playwright/index.test.ts +90 -0
- package/src/playwright/index.ts +73 -0
- package/src/release/__tests__/bootstrap-npm-packages.test.ts +63 -2
- package/src/release/bootstrap-npm-packages.ts +34 -0
- package/src/release/index.ts +18 -0
- package/src/wrangler/cloudflare.ts +289 -0
- package/src/wrangler/deploy-environment.test.ts +354 -0
- package/src/wrangler/deploy-environment.ts +445 -0
- package/src/wrangler/environment.test.ts +173 -0
- package/src/wrangler/environment.ts +366 -0
- package/managed/raw/patches/ttsc@0.19.3.patch +0 -67
|
@@ -486,7 +486,7 @@ function deployProductionStep(step: PublishWorkflowStep, options: PublishWorkflo
|
|
|
486
486
|
" ${{ steps.version.outputs.mode != 'none' && inputs.deploy_environment == 'production' && inputs.dry_run !=",
|
|
487
487
|
" 'true' }}",
|
|
488
488
|
...deployEnvLines(options),
|
|
489
|
-
' run: smoo github-ci nx-deploy --
|
|
489
|
+
' run: smoo github-ci nx-deploy --environment production --mode run-many --verify --name "Deploy Production"',
|
|
490
490
|
];
|
|
491
491
|
}
|
|
492
492
|
|
|
@@ -785,6 +785,11 @@ function renderMacosPlatformSteps(options: PublishWorkflowDefinitionOptions): st
|
|
|
785
785
|
` "${githubExpression('runner.temp')}/macos-platform-outputs"`,
|
|
786
786
|
'',
|
|
787
787
|
` # Step ${stepNumber++}`,
|
|
788
|
+
' - name: 🧪 Unit test macOS and iOS packages',
|
|
789
|
+
' run:',
|
|
790
|
+
` smoo github-ci nx-run-many --targets test --projects-with-targets "${MACOS_PLATFORM_TARGET_GLOBS.join(',')}"`,
|
|
791
|
+
'',
|
|
792
|
+
` # Step ${stepNumber++}`,
|
|
788
793
|
' - name: 📤 Upload macOS platform outputs',
|
|
789
794
|
' uses: actions/upload-artifact@v7.0.1',
|
|
790
795
|
' with:',
|
|
@@ -935,7 +940,7 @@ function renderFinalLinuxPublishSteps(options: PublishWorkflowDefinitionOptions)
|
|
|
935
940
|
" ${{ needs.linux-release-candidate.outputs.mode != 'none' && inputs.deploy_environment == 'production' &&",
|
|
936
941
|
" inputs.dry_run != 'true' }}",
|
|
937
942
|
...deployEnvLines(options),
|
|
938
|
-
' run: smoo github-ci nx-deploy --
|
|
943
|
+
' run: smoo github-ci nx-deploy --environment production --mode run-many --verify --name "Deploy Production"',
|
|
939
944
|
);
|
|
940
945
|
}
|
|
941
946
|
lines.push(
|
|
@@ -3,16 +3,16 @@ import type { PackageJson } from '../lib/json.js';
|
|
|
3
3
|
import { runtimeTypesRangeForPublishedVersions, validateRuntimePins } from './runtime.js';
|
|
4
4
|
|
|
5
5
|
describe('runtimeTypesRangeForPublishedVersions', () => {
|
|
6
|
-
test('
|
|
6
|
+
test('anchors a caret range at the newest published version in the installed Node major', () => {
|
|
7
7
|
expect(
|
|
8
8
|
runtimeTypesRangeForPublishedVersions('@types/node', '24.12.0', 'major', ['24.0.0', '24.12.4', '25.9.1']),
|
|
9
|
-
).toBe('
|
|
9
|
+
).toBe('^24.12.4');
|
|
10
10
|
});
|
|
11
11
|
|
|
12
|
-
test('
|
|
13
|
-
expect(
|
|
12
|
+
test('rejects fallback types from a different Node major', () => {
|
|
13
|
+
expect(() =>
|
|
14
14
|
runtimeTypesRangeForPublishedVersions('@types/node', '26.0.0', 'major', ['24.12.4', '25.9.0', '25.9.1']),
|
|
15
|
-
).
|
|
15
|
+
).toThrow('runtime major 26');
|
|
16
16
|
});
|
|
17
17
|
|
|
18
18
|
test('uses the installed Bun version when @types/bun has published it', () => {
|
|
@@ -23,10 +23,10 @@ describe('runtimeTypesRangeForPublishedVersions', () => {
|
|
|
23
23
|
expect(runtimeTypesRangeForPublishedVersions('@types/bun', '1.3.15', 'exact', ['1.3.13', '1.3.14'])).toBe('1.3.14');
|
|
24
24
|
});
|
|
25
25
|
|
|
26
|
-
test('ignores non-stable version strings when choosing the
|
|
27
|
-
expect(
|
|
28
|
-
'
|
|
29
|
-
);
|
|
26
|
+
test('ignores non-stable version strings when choosing the same-major anchor', () => {
|
|
27
|
+
expect(
|
|
28
|
+
runtimeTypesRangeForPublishedVersions('@types/node', '26.0.0', 'major', ['25.9.1', '26.0.2', '26.1.0-beta.1']),
|
|
29
|
+
).toBe('^26.0.2');
|
|
30
30
|
});
|
|
31
31
|
});
|
|
32
32
|
|
|
@@ -35,23 +35,29 @@ describe('validateRuntimePins', () => {
|
|
|
35
35
|
const aligned = () => ({
|
|
36
36
|
engines: { node: '>=24.0.0' },
|
|
37
37
|
packageManager: 'bun@1.3.14',
|
|
38
|
-
devDependencies: { '@types/node': '
|
|
38
|
+
devDependencies: { '@types/node': '^24.13.0' },
|
|
39
39
|
});
|
|
40
40
|
|
|
41
|
-
test('
|
|
42
|
-
|
|
41
|
+
test('accepts caret ranges anchored at any patch or minor in the PATH Node major', () => {
|
|
42
|
+
for (const version of ['^24.0.3', '^24.13.0']) {
|
|
43
|
+
const pkg = aligned();
|
|
44
|
+
pkg.devDependencies['@types/node'] = version;
|
|
45
|
+
expect(validateRuntimePins(pkg, runtime)).toBe(0);
|
|
46
|
+
}
|
|
43
47
|
});
|
|
44
48
|
|
|
45
49
|
test('fails when @types/node tracks a different major than the PATH node', () => {
|
|
46
50
|
const pkg = aligned();
|
|
47
|
-
pkg.devDependencies['@types/node'] = '
|
|
51
|
+
pkg.devDependencies['@types/node'] = '^26.1.1';
|
|
48
52
|
expect(validateRuntimePins(pkg, runtime)).toBe(1);
|
|
49
53
|
});
|
|
50
54
|
|
|
51
|
-
test('
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
+
test('rejects exact and tilde @types/node pins even when the major matches', () => {
|
|
56
|
+
for (const version of ['24.13.0', '~24.13.0']) {
|
|
57
|
+
const pkg = aligned();
|
|
58
|
+
pkg.devDependencies['@types/node'] = version;
|
|
59
|
+
expect(validateRuntimePins(pkg, runtime)).toBe(1);
|
|
60
|
+
}
|
|
55
61
|
});
|
|
56
62
|
|
|
57
63
|
test('fails engines.node and packageManager drift against the PATH runtimes', () => {
|
package/src/monorepo/runtime.ts
CHANGED
|
@@ -66,8 +66,8 @@ export async function syncRootRuntimeVersions(root: string): Promise<void> {
|
|
|
66
66
|
/**
|
|
67
67
|
* Validate that package.json runtime pins agree with the LIVE PATH runtimes,
|
|
68
68
|
* never a stored template. Offline by design: structural alignment only
|
|
69
|
-
* (
|
|
70
|
-
* consulted. Repair: `smoo monorepo init --runtime-only` inside the devenv shell.
|
|
69
|
+
* (Node types float within the runtime major; Bun types stay exact) — the registry
|
|
70
|
+
* is not consulted. Repair: `smoo monorepo init --runtime-only` inside the devenv shell.
|
|
71
71
|
*/
|
|
72
72
|
export async function validateRootRuntimeVersions(root: string): Promise<number> {
|
|
73
73
|
const packageJson = readJsonObject(join(root, 'package.json'));
|
|
@@ -100,15 +100,10 @@ export function validateRuntimePins(packageJson: PackageJson, runtime: RuntimeVe
|
|
|
100
100
|
failures++;
|
|
101
101
|
}
|
|
102
102
|
const typesNode = packageJson.devDependencies?.['@types/node'] ?? null;
|
|
103
|
-
const typesNodeParts = typesNode ?
|
|
103
|
+
const typesNodeParts = typesNode ? /^\^(\d+)\.(\d+)\.(\d+)$/.exec(typesNode) : null;
|
|
104
104
|
if (!typesNodeParts || typesNodeParts[1] !== nodeMajor) {
|
|
105
105
|
console.error(
|
|
106
|
-
`package.json @types/node is ${typesNode ?? 'missing'} but the PATH node is v${runtime.node} —
|
|
107
|
-
);
|
|
108
|
-
failures++;
|
|
109
|
-
} else if (typesNodeParts[2] === '0' && typesNodeParts[3] === '0') {
|
|
110
|
-
console.error(
|
|
111
|
-
`package.json @types/node is pinned to the ~${nodeMajor}.0.0 floor — tilde locks the first patch line and strands the repo on early broken releases; ${repair} to repin to the newest published ${nodeMajor}.x`,
|
|
106
|
+
`package.json @types/node is ${typesNode ?? 'missing'} but the PATH node is v${runtime.node} — expected a caret range within the runtime major (^${nodeMajor}.x.y); ${repair}`,
|
|
112
107
|
);
|
|
113
108
|
failures++;
|
|
114
109
|
}
|
|
@@ -144,15 +139,15 @@ export function runtimeTypesRangeForPublishedVersions(
|
|
|
144
139
|
|
|
145
140
|
if (pinMode === 'major') {
|
|
146
141
|
const runtimeMajor = parsedRuntimeVersion[0].toString();
|
|
147
|
-
//
|
|
148
|
-
//
|
|
149
|
-
// broken early release (e.g. @types/node 24.0.x's URLPattern/DOM TS2403
|
|
150
|
-
// conflict, fixed in 24.13) with no path to the repaired minors.
|
|
142
|
+
// Anchor the range at the newest published types in the runtime major. The caret
|
|
143
|
+
// admits later patches and minors without crossing into a different Node major.
|
|
151
144
|
const latestInMajor = latestVersion(versions.filter((version) => versionMajor(version) === runtimeMajor));
|
|
152
|
-
if (latestInMajor) {
|
|
153
|
-
|
|
145
|
+
if (!latestInMajor) {
|
|
146
|
+
throw new Error(`Unable to find published ${packageName} versions for runtime major ${runtimeMajor}`);
|
|
154
147
|
}
|
|
155
|
-
|
|
148
|
+
return `^${latestInMajor}`;
|
|
149
|
+
}
|
|
150
|
+
if (versions.includes(runtimeVersion)) {
|
|
156
151
|
return runtimeVersion;
|
|
157
152
|
}
|
|
158
153
|
|
|
@@ -162,7 +157,7 @@ export function runtimeTypesRangeForPublishedVersions(
|
|
|
162
157
|
}
|
|
163
158
|
|
|
164
159
|
console.warn(`${packageName} has no published types for runtime ${runtimeVersion}; using ${latest}`);
|
|
165
|
-
return
|
|
160
|
+
return latest;
|
|
166
161
|
}
|
|
167
162
|
|
|
168
163
|
function latestVersion(versions: readonly string[]): string | null {
|
|
@@ -3,10 +3,12 @@ import { mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
|
|
|
3
3
|
import { tmpdir } from 'node:os';
|
|
4
4
|
import { dirname, join } from 'node:path';
|
|
5
5
|
import {
|
|
6
|
+
applyDevenvPackageDefaults,
|
|
6
7
|
applyRootDevDependencyDefaults,
|
|
7
8
|
applyToolConfigDefaults,
|
|
8
9
|
applyToolingPackageDefaults,
|
|
9
10
|
readToolContext,
|
|
11
|
+
validateDevenvPackages,
|
|
10
12
|
validateToolConfig,
|
|
11
13
|
} from './tool-validation.js';
|
|
12
14
|
|
|
@@ -19,7 +21,7 @@ const registryVersions: Record<string, string[]> = {
|
|
|
19
21
|
'eslint-stdout': ['1.1.1', '1.1.2'],
|
|
20
22
|
nx: ['23.1.0'],
|
|
21
23
|
prettier: ['3.6.1'],
|
|
22
|
-
ttsc: ['0.
|
|
24
|
+
ttsc: ['0.25.0'],
|
|
23
25
|
typescript: ['6.0.3'],
|
|
24
26
|
// alias package is not fetched from registry under this name when using fallback
|
|
25
27
|
'@typescript/native': ['7.0.2'],
|
|
@@ -50,6 +52,9 @@ describe('tool configuration validation', () => {
|
|
|
50
52
|
devDependencies: {
|
|
51
53
|
'@smoothbricks/cli': 'workspace:*',
|
|
52
54
|
},
|
|
55
|
+
patchedDependencies: {
|
|
56
|
+
'ttsc@0.19.3': 'patches/ttsc@0.19.3.patch',
|
|
57
|
+
},
|
|
53
58
|
});
|
|
54
59
|
const devenvPath = join(root, 'tooling/direnv/devenv.nix');
|
|
55
60
|
await mkdir(dirname(devenvPath), { recursive: true });
|
|
@@ -76,22 +81,104 @@ describe('tool configuration validation', () => {
|
|
|
76
81
|
expect(rootPackage.devDependencies['@smoothbricks/nx-plugin']).toBe('workspace:*');
|
|
77
82
|
expect(rootPackage.devDependencies['eslint-stdout']).toBe('workspace:*');
|
|
78
83
|
expect(rootPackage.devDependencies.nx).toBe('23.1.0');
|
|
79
|
-
expect(rootPackage.devDependencies.ttsc).toBe('0.
|
|
84
|
+
expect(rootPackage.devDependencies.ttsc).toBe('0.25.0');
|
|
80
85
|
expect(rootPackage.devDependencies.typescript).toBe('^6.0.3');
|
|
81
86
|
expect(rootPackage.devDependencies['@typescript/native']).toBe('npm:typescript@^7.0.2');
|
|
82
|
-
expect(rootPackage.patchedDependencies['ttsc@0.19.3']).
|
|
87
|
+
expect(rootPackage.patchedDependencies?.['ttsc@0.19.3']).toBeUndefined();
|
|
83
88
|
expect(rootPackage.workspaces).toContain('tooling');
|
|
84
89
|
expect(toolingPackage.name).toBe('@smoothbricks/tooling');
|
|
85
90
|
expect(toolingPackage.dependencies['@smoothbricks/cli']).toBe('workspace:*');
|
|
86
91
|
expect(devenv).toContain('nodejs_latest');
|
|
87
92
|
expect(devenv).toContain('coreutils');
|
|
88
93
|
expect(devenv).toContain('git-format-staged');
|
|
94
|
+
expect(devenv).toContain('go # Builds ttsc source plugins');
|
|
95
|
+
expect(devenv).not.toContain('sccache');
|
|
96
|
+
} finally {
|
|
97
|
+
await rm(root, { recursive: true, force: true });
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('requires Go for managed TypeScript source-plugin builds without forcing Rust tools', async () => {
|
|
102
|
+
const root = await mkdtemp(join(tmpdir(), 'smoo-tool-validation-'));
|
|
103
|
+
try {
|
|
104
|
+
const devenvPath = join(root, 'tooling/direnv/devenv.nix');
|
|
105
|
+
await mkdir(dirname(devenvPath), { recursive: true });
|
|
106
|
+
await writeFile(
|
|
107
|
+
devenvPath,
|
|
108
|
+
`{
|
|
109
|
+
pkgs,
|
|
110
|
+
...
|
|
111
|
+
}: {
|
|
112
|
+
packages = with pkgs; [
|
|
113
|
+
nodejs_latest
|
|
114
|
+
bun
|
|
115
|
+
git
|
|
116
|
+
git-format-staged
|
|
117
|
+
jq
|
|
118
|
+
alejandra
|
|
119
|
+
coreutils
|
|
120
|
+
gnutar
|
|
121
|
+
];
|
|
122
|
+
}
|
|
123
|
+
`,
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
expect(validateDevenvPackages(root)).toBe(1);
|
|
127
|
+
applyDevenvPackageDefaults(root);
|
|
128
|
+
expect(validateDevenvPackages(root)).toBe(0);
|
|
129
|
+
const devenv = await readFile(devenvPath, 'utf8');
|
|
130
|
+
expect(devenv).toContain('go # Builds ttsc source plugins');
|
|
131
|
+
expect(devenv).not.toContain('sccache');
|
|
89
132
|
} finally {
|
|
90
133
|
await rm(root, { recursive: true, force: true });
|
|
91
134
|
}
|
|
92
135
|
});
|
|
93
136
|
|
|
94
|
-
it('
|
|
137
|
+
it('requires Rust compiler helpers only when the workspace contains Cargo manifests', async () => {
|
|
138
|
+
const root = await mkdtemp(join(tmpdir(), 'smoo-tool-validation-'));
|
|
139
|
+
try {
|
|
140
|
+
const crateRoot = join(root, 'packages/native');
|
|
141
|
+
await mkdir(crateRoot, { recursive: true });
|
|
142
|
+
await writeFile(join(crateRoot, 'Cargo.toml'), '[package]\nname = "native"\nversion = "0.0.0"\n');
|
|
143
|
+
const devenvPath = join(root, 'tooling/direnv/devenv.nix');
|
|
144
|
+
await mkdir(dirname(devenvPath), { recursive: true });
|
|
145
|
+
await writeFile(
|
|
146
|
+
devenvPath,
|
|
147
|
+
`{
|
|
148
|
+
pkgs,
|
|
149
|
+
lib,
|
|
150
|
+
...
|
|
151
|
+
}: {
|
|
152
|
+
packages = with pkgs; [
|
|
153
|
+
nodejs_latest
|
|
154
|
+
bun
|
|
155
|
+
git
|
|
156
|
+
git-format-staged
|
|
157
|
+
jq
|
|
158
|
+
alejandra
|
|
159
|
+
coreutils
|
|
160
|
+
gnutar
|
|
161
|
+
go
|
|
162
|
+
stdenv.cc.cc.lib
|
|
163
|
+
];
|
|
164
|
+
}
|
|
165
|
+
`,
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
expect(validateDevenvPackages(root)).toBe(2);
|
|
169
|
+
applyDevenvPackageDefaults(root);
|
|
170
|
+
expect(validateDevenvPackages(root)).toBe(0);
|
|
171
|
+
const devenv = await readFile(devenvPath, 'utf8');
|
|
172
|
+
expect(devenv).toContain('sccache # Rust compiler wrapper and cache');
|
|
173
|
+
expect(devenv).toContain('++ lib.optionals pkgs.stdenv.isLinux [');
|
|
174
|
+
expect(devenv).toContain('pkgs.stdenv.cc');
|
|
175
|
+
expect(devenv).toContain('stdenv.cc.cc.lib');
|
|
176
|
+
} finally {
|
|
177
|
+
await rm(root, { recursive: true, force: true });
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it('accepts newer dependencies after removing even an empty obsolete ttsc patch key', async () => {
|
|
95
182
|
const root = await mkdtemp(join(tmpdir(), 'smoo-tool-validation-'));
|
|
96
183
|
try {
|
|
97
184
|
await writeJson(join(root, 'package.json'), {
|
|
@@ -108,12 +195,12 @@ describe('tool configuration validation', () => {
|
|
|
108
195
|
'eslint-stdout': await currentEslintStdoutRange(),
|
|
109
196
|
nx: '24.0.0',
|
|
110
197
|
prettier: '^3.7.0',
|
|
111
|
-
ttsc: '0.
|
|
198
|
+
ttsc: '0.25.0',
|
|
112
199
|
typescript: '^6.0.0',
|
|
113
200
|
'@typescript/native': 'npm:typescript@^7.0.2',
|
|
114
201
|
},
|
|
115
202
|
patchedDependencies: {
|
|
116
|
-
'ttsc@0.19.3': '
|
|
203
|
+
'ttsc@0.19.3': '',
|
|
117
204
|
},
|
|
118
205
|
});
|
|
119
206
|
await writeJson(join(root, 'tooling/package.json'), {
|
|
@@ -138,12 +225,17 @@ describe('tool configuration validation', () => {
|
|
|
138
225
|
alejandra
|
|
139
226
|
coreutils
|
|
140
227
|
gnutar
|
|
228
|
+
go
|
|
141
229
|
];
|
|
142
230
|
}
|
|
143
231
|
`,
|
|
144
232
|
);
|
|
145
233
|
|
|
234
|
+
expect(await validateToolConfig(root)).toBe(1);
|
|
235
|
+
await applyToolConfigDefaults(root);
|
|
146
236
|
expect(await validateToolConfig(root)).toBe(0);
|
|
237
|
+
const rootPackage = JSON.parse(await readFile(join(root, 'package.json'), 'utf8'));
|
|
238
|
+
expect(rootPackage.patchedDependencies?.['ttsc@0.19.3']).toBeUndefined();
|
|
147
239
|
} finally {
|
|
148
240
|
await rm(root, { recursive: true, force: true });
|
|
149
241
|
}
|
|
@@ -210,7 +302,7 @@ describe('tool configuration validation', () => {
|
|
|
210
302
|
eslint: '^10.0.0',
|
|
211
303
|
nx: '24.0.0',
|
|
212
304
|
prettier: '^3.7.0',
|
|
213
|
-
ttsc: '0.
|
|
305
|
+
ttsc: '0.25.0',
|
|
214
306
|
typescript: '^6.0.0',
|
|
215
307
|
},
|
|
216
308
|
});
|
|
@@ -224,7 +316,7 @@ describe('tool configuration validation', () => {
|
|
|
224
316
|
expect(rootPackage.devDependencies['eslint-stdout']).toBe(await currentEslintStdoutRange());
|
|
225
317
|
expect(rootPackage.devDependencies['@smoothbricks/nx-plugin']).toBe(await currentNxPluginRange());
|
|
226
318
|
expect(rootPackage.devDependencies['@typescript/native']).toBe('npm:typescript@^7.0.2');
|
|
227
|
-
expect(rootPackage.patchedDependencies?.['ttsc@0.19.3']).
|
|
319
|
+
expect(rootPackage.patchedDependencies?.['ttsc@0.19.3']).toBeUndefined();
|
|
228
320
|
expect(toolingPackage.name).toBe('@fixture/tooling');
|
|
229
321
|
expect(toolingPackage.dependencies['@smoothbricks/cli']).toBe(await currentCliRange());
|
|
230
322
|
} finally {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
1
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
2
|
import { dirname, join } from 'node:path';
|
|
3
3
|
import typia from 'typia';
|
|
4
4
|
import { cliPackageVersion, isSmoothBricksCodebasePackageName } from '../lib/cli-package.js';
|
|
@@ -56,8 +56,7 @@ const rootDevDependencies: RequiredDependency[] = [
|
|
|
56
56
|
},
|
|
57
57
|
{ name: 'nx', fallbackVersion: '23.1.0', minimumVersion: '23.1.0' },
|
|
58
58
|
{ name: 'prettier', fallbackVersion: '^3.6.1', minimumVersion: '3.6.0', prefix: '^' },
|
|
59
|
-
|
|
60
|
-
{ name: 'ttsc', fallbackVersion: '0.19.3' },
|
|
59
|
+
{ name: 'ttsc', fallbackVersion: '0.25.0' },
|
|
61
60
|
// Nx and typescript-eslint still load the TypeScript JS API (6.x).
|
|
62
61
|
// Compilation is exclusively delegated to ttsc by the Nx plugin targets.
|
|
63
62
|
{ name: 'typescript', fallbackVersion: '^6.0.3', minimumVersion: '6.0.0', prefix: '^' },
|
|
@@ -68,15 +67,26 @@ const rootDevDependencies: RequiredDependency[] = [
|
|
|
68
67
|
|
|
69
68
|
const cliPackageName = '@smoothbricks/cli';
|
|
70
69
|
|
|
71
|
-
const requiredDevenvPackages = ['bun', 'git', 'git-format-staged', 'jq', 'alejandra', 'coreutils', 'gnutar'];
|
|
70
|
+
const requiredDevenvPackages = ['bun', 'git', 'git-format-staged', 'jq', 'alejandra', 'coreutils', 'gnutar', 'go'];
|
|
72
71
|
// Any explicit nodejs provider is fine — the pinned major is the repo's choice
|
|
73
72
|
// (Lambda parity, org convergence, …). Whether the pins in package.json agree
|
|
74
73
|
// with the runtime is validated against the live PATH (validateRootRuntimeVersions),
|
|
75
74
|
// never against a version template here.
|
|
76
75
|
const nodePackagePattern = /(^|\s)nodejs(_\d+|_latest)?(\s|#|$)/m;
|
|
77
|
-
|
|
78
|
-
const
|
|
79
|
-
const
|
|
76
|
+
const requiredRustDevenvPackages = ['sccache'];
|
|
77
|
+
const linuxCompilerPackage = 'pkgs.stdenv.cc';
|
|
78
|
+
const ignoredNativeManifestDirectories = new Set([
|
|
79
|
+
'.devenv',
|
|
80
|
+
'.direnv',
|
|
81
|
+
'.git',
|
|
82
|
+
'.nx',
|
|
83
|
+
'dist',
|
|
84
|
+
'node_modules',
|
|
85
|
+
'target',
|
|
86
|
+
'vendor',
|
|
87
|
+
]);
|
|
88
|
+
|
|
89
|
+
const obsoleteTtscPatchedDependencyKey = 'ttsc@0.19.3';
|
|
80
90
|
|
|
81
91
|
export async function applyToolConfigDefaults(root: string): Promise<void> {
|
|
82
92
|
const context = await readToolContext(root);
|
|
@@ -89,7 +99,7 @@ export async function validateToolConfig(root: string): Promise<number> {
|
|
|
89
99
|
const context = await readToolContext(root);
|
|
90
100
|
return (
|
|
91
101
|
validateRootDevDependencies(context.policy, context.rootPackage) +
|
|
92
|
-
|
|
102
|
+
validateObsoleteTtscPatchRemoved(context.rootPackage) +
|
|
93
103
|
validateToolingPackage(root, context.policy) +
|
|
94
104
|
validateToolingWorkspace(context.rootPackage) +
|
|
95
105
|
validateDevenvPackages(root)
|
|
@@ -113,7 +123,7 @@ export async function applyRootDevDependencyDefaults(root: string, context: Tool
|
|
|
113
123
|
if (delete devDependencies['@smoothbricks/cli']) {
|
|
114
124
|
changed = true;
|
|
115
125
|
}
|
|
116
|
-
changed =
|
|
126
|
+
changed = removeObsoleteTtscPatch(pkg) || changed;
|
|
117
127
|
if (changed) {
|
|
118
128
|
writeJsonObject(join(root, 'package.json'), pkg);
|
|
119
129
|
console.log('updated package.json workspace tool dependencies');
|
|
@@ -129,7 +139,6 @@ async function applyRootPackageToolDefaults(root: string, context: ToolContext):
|
|
|
129
139
|
}
|
|
130
140
|
let dependencyChanged = false;
|
|
131
141
|
let workspaceChanged = false;
|
|
132
|
-
let patchChanged = false;
|
|
133
142
|
const devDependencies = ensureDependencyMap(pkg, 'devDependencies');
|
|
134
143
|
for (const dependency of rootDevDependencies) {
|
|
135
144
|
const current = devDependencies[dependency.name];
|
|
@@ -142,8 +151,8 @@ async function applyRootPackageToolDefaults(root: string, context: ToolContext):
|
|
|
142
151
|
dependencyChanged = true;
|
|
143
152
|
}
|
|
144
153
|
workspaceChanged = addWorkspacePattern(pkg, 'tooling');
|
|
145
|
-
|
|
146
|
-
if (dependencyChanged || workspaceChanged
|
|
154
|
+
dependencyChanged = removeObsoleteTtscPatch(pkg) || dependencyChanged;
|
|
155
|
+
if (dependencyChanged || workspaceChanged) {
|
|
147
156
|
writeJsonObject(join(root, 'package.json'), pkg);
|
|
148
157
|
}
|
|
149
158
|
console.log(
|
|
@@ -156,11 +165,6 @@ async function applyRootPackageToolDefaults(root: string, context: ToolContext):
|
|
|
156
165
|
? 'updated package.json tooling workspace'
|
|
157
166
|
: 'unchanged package.json tooling workspace',
|
|
158
167
|
);
|
|
159
|
-
console.log(
|
|
160
|
-
patchChanged
|
|
161
|
-
? 'updated package.json patchedDependencies for ttsc'
|
|
162
|
-
: 'unchanged package.json patchedDependencies for ttsc',
|
|
163
|
-
);
|
|
164
168
|
}
|
|
165
169
|
|
|
166
170
|
export function applyToolingPackageDefaults(root: string, policy: ToolPolicy): void {
|
|
@@ -209,7 +213,9 @@ export function applyDevenvPackageDefaults(root: string): void {
|
|
|
209
213
|
changed = next !== content || changed;
|
|
210
214
|
content = next;
|
|
211
215
|
}
|
|
212
|
-
|
|
216
|
+
const compilesRust = workspaceCompilesRust(root);
|
|
217
|
+
const requiredPackages = [...requiredDevenvPackages, ...(compilesRust ? requiredRustDevenvPackages : [])];
|
|
218
|
+
for (const name of requiredPackages) {
|
|
213
219
|
if (hasNixPackage(content, name)) {
|
|
214
220
|
continue;
|
|
215
221
|
}
|
|
@@ -217,6 +223,11 @@ export function applyDevenvPackageDefaults(root: string): void {
|
|
|
217
223
|
changed = next !== content || changed;
|
|
218
224
|
content = next;
|
|
219
225
|
}
|
|
226
|
+
if (compilesRust && !hasLinuxCompilerPackage(content)) {
|
|
227
|
+
const next = addLinuxCompilerPackage(content);
|
|
228
|
+
changed = next !== content || changed;
|
|
229
|
+
content = next;
|
|
230
|
+
}
|
|
220
231
|
if (changed) {
|
|
221
232
|
writeFileSync(path, content);
|
|
222
233
|
console.log('updated tooling/direnv/devenv.nix packages');
|
|
@@ -252,22 +263,25 @@ export function validateRootDevDependencies(policy: ToolPolicy, rootPackage: Pac
|
|
|
252
263
|
return failures;
|
|
253
264
|
}
|
|
254
265
|
|
|
255
|
-
function
|
|
256
|
-
const
|
|
257
|
-
|
|
266
|
+
function removeObsoleteTtscPatch(pkg: PackageJson): boolean {
|
|
267
|
+
const patchedDependencies = pkg.patchedDependencies;
|
|
268
|
+
if (!patchedDependencies || !(obsoleteTtscPatchedDependencyKey in patchedDependencies)) {
|
|
269
|
+
return false;
|
|
270
|
+
}
|
|
271
|
+
delete patchedDependencies[obsoleteTtscPatchedDependencyKey];
|
|
272
|
+
if (Object.keys(patchedDependencies).length === 0) {
|
|
273
|
+
delete pkg.patchedDependencies;
|
|
274
|
+
}
|
|
275
|
+
return true;
|
|
258
276
|
}
|
|
259
277
|
|
|
260
|
-
function
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
}
|
|
264
|
-
const actual = rootPackage.patchedDependencies?.[TTSC_PATCHED_DEPENDENCY_KEY];
|
|
265
|
-
if (actual === TTSC_PATCH_PATH) {
|
|
278
|
+
function validateObsoleteTtscPatchRemoved(rootPackage: PackageJson | null): number {
|
|
279
|
+
const patchedDependencies = rootPackage?.patchedDependencies;
|
|
280
|
+
if (!patchedDependencies || !(obsoleteTtscPatchedDependencyKey in patchedDependencies)) {
|
|
266
281
|
return 0;
|
|
267
282
|
}
|
|
268
283
|
console.error(
|
|
269
|
-
`package.json patchedDependencies.${
|
|
270
|
-
(typeof actual === 'string' ? `; found ${actual}` : ' (required for declaration-emit fix)'),
|
|
284
|
+
`package.json patchedDependencies.${obsoleteTtscPatchedDependencyKey} must be removed; ttsc 0.25.0 emits typia declarations without the obsolete patch`,
|
|
271
285
|
);
|
|
272
286
|
return 1;
|
|
273
287
|
}
|
|
@@ -321,12 +335,20 @@ export function validateDevenvPackages(root: string): number {
|
|
|
321
335
|
);
|
|
322
336
|
failures++;
|
|
323
337
|
}
|
|
324
|
-
|
|
338
|
+
const compilesRust = workspaceCompilesRust(root);
|
|
339
|
+
const requiredPackages = [...requiredDevenvPackages, ...(compilesRust ? requiredRustDevenvPackages : [])];
|
|
340
|
+
for (const name of requiredPackages) {
|
|
325
341
|
if (!hasNixPackage(content, name)) {
|
|
326
342
|
console.error(`tooling/direnv/devenv.nix packages must include ${name}`);
|
|
327
343
|
failures++;
|
|
328
344
|
}
|
|
329
345
|
}
|
|
346
|
+
if (compilesRust && !hasLinuxCompilerPackage(content)) {
|
|
347
|
+
console.error(
|
|
348
|
+
'tooling/direnv/devenv.nix packages must include pkgs.stdenv.cc in a Linux-only package block for Rust builds',
|
|
349
|
+
);
|
|
350
|
+
failures++;
|
|
351
|
+
}
|
|
330
352
|
return failures;
|
|
331
353
|
}
|
|
332
354
|
|
|
@@ -335,7 +357,9 @@ function addNixPackage(content: string, name: string, comment: string): string {
|
|
|
335
357
|
if (hasNixPackage(content, name)) {
|
|
336
358
|
return content;
|
|
337
359
|
}
|
|
338
|
-
const
|
|
360
|
+
const directPackagesStart = content.indexOf(' packages = with pkgs; [');
|
|
361
|
+
const groupedPackagesStart = content.indexOf(' (with pkgs; [');
|
|
362
|
+
const packagesStart = directPackagesStart === -1 ? groupedPackagesStart : directPackagesStart;
|
|
339
363
|
if (packagesStart === -1) {
|
|
340
364
|
return content;
|
|
341
365
|
}
|
|
@@ -347,6 +371,60 @@ function hasNixPackage(content: string, name: string): boolean {
|
|
|
347
371
|
return new RegExp(`(^|\\s)${escapeRegex(name)}(\\s|#|$)`, 'm').test(content);
|
|
348
372
|
}
|
|
349
373
|
|
|
374
|
+
function workspaceCompilesRust(root: string): boolean {
|
|
375
|
+
return containsCargoManifest(root);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
function containsCargoManifest(directory: string): boolean {
|
|
379
|
+
for (const entry of readdirSync(directory, { withFileTypes: true })) {
|
|
380
|
+
if (entry.isFile() && entry.name === 'Cargo.toml') {
|
|
381
|
+
return true;
|
|
382
|
+
}
|
|
383
|
+
if (
|
|
384
|
+
entry.isDirectory() &&
|
|
385
|
+
entry.name.startsWith('.') === false &&
|
|
386
|
+
ignoredNativeManifestDirectories.has(entry.name) === false &&
|
|
387
|
+
containsCargoManifest(join(directory, entry.name))
|
|
388
|
+
) {
|
|
389
|
+
return true;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
return false;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
function hasLinuxCompilerPackage(content: string): boolean {
|
|
396
|
+
const linuxPackages = /\+\+\s+lib\.optionals\s+pkgs\.stdenv\.isLinux\s+\[([\s\S]*?)\]/g;
|
|
397
|
+
return [...content.matchAll(linuxPackages)].some((match) => hasNixPackage(match[1] ?? '', linuxCompilerPackage));
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
function addLinuxCompilerPackage(content: string): string {
|
|
401
|
+
if (hasLinuxCompilerPackage(content)) {
|
|
402
|
+
return content;
|
|
403
|
+
}
|
|
404
|
+
const directStart = content.indexOf(' packages = with pkgs; [');
|
|
405
|
+
if (directStart !== -1) {
|
|
406
|
+
const listEnd = content.indexOf('\n ];', directStart);
|
|
407
|
+
if (listEnd === -1) {
|
|
408
|
+
return content;
|
|
409
|
+
}
|
|
410
|
+
const listBody = content.slice(directStart + ' packages = with pkgs; ['.length, listEnd);
|
|
411
|
+
const replacement = ` packages =\n (with pkgs; [${listBody}\n ])\n ++ lib.optionals pkgs.stdenv.isLinux [\n ${linuxCompilerPackage}\n ];`;
|
|
412
|
+
return `${content.slice(0, directStart)}${replacement}${content.slice(listEnd + '\n ];'.length)}`;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
const packagesStart = content.indexOf(' packages =');
|
|
416
|
+
const nextOption = content.indexOf('\n\n ', packagesStart);
|
|
417
|
+
if (packagesStart === -1 || nextOption === -1) {
|
|
418
|
+
return content;
|
|
419
|
+
}
|
|
420
|
+
const terminator = content.lastIndexOf(';', nextOption);
|
|
421
|
+
if (terminator < packagesStart) {
|
|
422
|
+
return content;
|
|
423
|
+
}
|
|
424
|
+
const linuxPackages = `\n ++ lib.optionals pkgs.stdenv.isLinux [\n ${linuxCompilerPackage}\n ]`;
|
|
425
|
+
return `${content.slice(0, terminator)}${linuxPackages}${content.slice(terminator)}`;
|
|
426
|
+
}
|
|
427
|
+
|
|
350
428
|
function nixPackageComment(name: string): string {
|
|
351
429
|
if (name === 'coreutils') {
|
|
352
430
|
return '# Provides fmt for commit message wrapping';
|
|
@@ -357,6 +435,12 @@ function nixPackageComment(name: string): string {
|
|
|
357
435
|
if (name === 'git') {
|
|
358
436
|
return '# Git hooks and repository inspection';
|
|
359
437
|
}
|
|
438
|
+
if (name === 'go') {
|
|
439
|
+
return '# Builds ttsc source plugins';
|
|
440
|
+
}
|
|
441
|
+
if (name === 'sccache') {
|
|
442
|
+
return '# Rust compiler wrapper and cache';
|
|
443
|
+
}
|
|
360
444
|
return '';
|
|
361
445
|
}
|
|
362
446
|
|