@smoothbricks/cli 0.4.1 → 0.4.3

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.
@@ -1 +1 @@
1
- {"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../src/lib/run.ts"],"names":[],"mappings":"AAMA,wBAAsB,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAKnH;AAED,wBAAsB,SAAS,CAC7B,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EAAE,EACd,GAAG,EAAE,MAAM,EACX,KAAK,UAAQ,EACb,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC3B,OAAO,CAAC,MAAM,CAAC,CAQjB;AAED,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EAAE,EACd,GAAG,EAAE,MAAM,EACX,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC3B,OAAO,CAAC,MAAM,CAAC,CAiBjB;AAED,wBAAsB,SAAS,CAC7B,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EAAE,EACd,GAAG,EAAE,MAAM,EACX,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC3B,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAY/D;AAED,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAOvE;AAuCD,wBAAsB,YAAY,IAAI,OAAO,CAAC,MAAM,CAAC,CAMpD;AAED,wBAAgB,MAAM,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAEhD"}
1
+ {"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../src/lib/run.ts"],"names":[],"mappings":"AAMA,wBAAsB,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAKnH;AAED,wBAAsB,SAAS,CAC7B,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EAAE,EACd,GAAG,EAAE,MAAM,EACX,KAAK,UAAQ,EACb,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC3B,OAAO,CAAC,MAAM,CAAC,CAQjB;AAED,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EAAE,EACd,GAAG,EAAE,MAAM,EACX,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC3B,OAAO,CAAC,MAAM,CAAC,CAiBjB;AAED,wBAAsB,SAAS,CAC7B,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EAAE,EACd,GAAG,EAAE,MAAM,EACX,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC3B,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAY/D;AAED,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAOvE;AAmCD,wBAAsB,YAAY,IAAI,OAAO,CAAC,MAAM,CAAC,CAMpD;AAED,wBAAgB,MAAM,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAEhD"}
package/dist/lib/run.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { spawn } from 'node:child_process';
2
2
  import { existsSync } from 'node:fs';
3
- import { dirname, join } from 'node:path';
3
+ import { join } from 'node:path';
4
4
  import { fileURLToPath } from 'node:url';
5
5
  import { $ } from 'bun';
6
6
  export async function run(command, args, cwd, env) {
@@ -82,10 +82,6 @@ function resolveBundledCommand(command) {
82
82
  if (command === 'sherif') {
83
83
  return fileURLToPath(import.meta.resolve('sherif'));
84
84
  }
85
- if (command === 'attw') {
86
- const packageJson = fileURLToPath(import.meta.resolve('@arethetypeswrong/cli/package.json'));
87
- return join(dirname(packageJson), 'dist', 'index.js');
88
- }
89
85
  }
90
86
  catch {
91
87
  return null;
@@ -1 +1 @@
1
- {"version":3,"file":"managed-files.d.ts","sourceRoot":"","sources":["../../src/monorepo/managed-files.ts"],"names":[],"mappings":"AAkBA,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,SAAS,GAAG,SAAS,GAAG,WAAW,GAAG,SAAS,GAAG,iBAAiB,GAAG,SAAS,GAAG,YAAY,CAAC;CACxG;AAwFD,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,GAAG,OAAO,GAAG,MAAM,GAAG,UAAU,EAAE,CAG/F;AA4KD,wBAAgB,YAAY,CAAC,OAAO,EAAE,UAAU,EAAE,GAAG,IAAI,CAIxD;AAED,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAQzD"}
1
+ {"version":3,"file":"managed-files.d.ts","sourceRoot":"","sources":["../../src/monorepo/managed-files.ts"],"names":[],"mappings":"AAkBA,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,SAAS,GAAG,SAAS,GAAG,WAAW,GAAG,SAAS,GAAG,iBAAiB,GAAG,SAAS,GAAG,YAAY,CAAC;CACxG;AAmGD,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,GAAG,OAAO,GAAG,MAAM,GAAG,UAAU,EAAE,CAG/F;AA4KD,wBAAgB,YAAY,CAAC,OAAO,EAAE,UAAU,EAAE,GAAG,IAAI,CAIxD;AAED,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAQzD"}
@@ -6,6 +6,11 @@ import { listReleasePackages, readPackageJson } from '../lib/workspace.js';
6
6
  import { renderCiWorkflowYaml } from './ci-workflow.js';
7
7
  import { renderPublishWorkflowYaml } from './publish-workflow.js';
8
8
  const managedFiles = [
9
+ {
10
+ kind: 'raw',
11
+ source: 'envrc',
12
+ target: '.envrc',
13
+ },
9
14
  {
10
15
  kind: 'raw',
11
16
  source: 'tooling/direnv/repo-path',
@@ -18,6 +23,12 @@ const managedFiles = [
18
23
  target: 'tooling/direnv/github-actions-bootstrap.sh',
19
24
  executable: true,
20
25
  },
26
+ {
27
+ kind: 'raw',
28
+ source: 'tooling/direnv/setup-environment.ts',
29
+ target: 'tooling/direnv/setup-environment.ts',
30
+ executable: true,
31
+ },
21
32
  {
22
33
  kind: 'raw',
23
34
  source: 'tooling/git-hooks/pre-commit.sh',
@@ -1 +1 @@
1
- {"version":3,"file":"packed-package.d.ts","sourceRoot":"","sources":["../../src/monorepo/packed-package.ts"],"names":[],"mappings":"AAUA,wBAAsB,4BAA4B,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAMhF;AAED,wBAAsB,kCAAkC,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAMtF;AAED,wBAAsB,mCAAmC,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAMvF;AAED,wBAAsB,gCAAgC,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAMpF"}
1
+ {"version":3,"file":"packed-package.d.ts","sourceRoot":"","sources":["../../src/monorepo/packed-package.ts"],"names":[],"mappings":"AAYA,wBAAsB,4BAA4B,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAMhF;AAED,wBAAsB,kCAAkC,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAMtF;AAED,wBAAsB,mCAAmC,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAMvF;AAED,wBAAsB,gCAAgC,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAMpF"}
@@ -1,5 +1,7 @@
1
- import { readFileSync, rmSync, unlinkSync } from 'node:fs';
2
- import { join } from 'node:path';
1
+ import { mkdtempSync, readdirSync, readFileSync, rmSync, statSync, unlinkSync } from 'node:fs';
2
+ import { tmpdir } from 'node:os';
3
+ import { dirname, join, relative } from 'node:path';
4
+ import { fileURLToPath, pathToFileURL } from 'node:url';
3
5
  import { publint } from 'publint';
4
6
  import { formatMessage } from 'publint/utils';
5
7
  import { isRecord } from '../lib/json.js';
@@ -65,37 +67,143 @@ async function validatePackedManifest(root, pkg, packed) {
65
67
  return failures;
66
68
  }
67
69
  async function validateAttw(root, pkg, packed) {
68
- const attwArgs = [
69
- packed.path,
70
- '--format',
71
- 'ascii',
72
- '--no-color',
73
- '--profile',
74
- 'node16',
75
- '--ignore-rules',
76
- 'cjs-resolves-to-esm',
77
- ...attwExcludedEntrypointArgs(pkg),
78
- ];
79
- const attw = await runResult('attw', attwArgs, root);
80
- if (attw.exitCode === 0) {
70
+ const attw = await loadAttwCore();
71
+ const analysis = await attw.checkPackage(await createAttwPackageFromTarball(attw, root, packed.path), {
72
+ excludeEntrypoints: wasmExportEntrypoints(pkg.json.exports),
73
+ });
74
+ if (!isRecord(analysis) || analysis.types === false) {
81
75
  return 0;
82
76
  }
83
- printAttwOutput(attw.stdout);
84
- printAttwOutput(attw.stderr);
77
+ const rawProblems = Array.isArray(analysis.problems) ? analysis.problems : [];
78
+ const problems = rawProblems.filter(isReportedAttwProblem);
79
+ if (problems.length === 0) {
80
+ return 0;
81
+ }
82
+ for (const problem of problems) {
83
+ console.error(`${pkg.path}: are-the-types-wrong ${problem.kind}${formatProblemLocation(problem)}`);
84
+ }
85
85
  console.error(`${pkg.path}: are-the-types-wrong validation failed`);
86
86
  return 1;
87
87
  }
88
- function printAttwOutput(output) {
89
- for (const line of output.split('\n')) {
90
- if (!line || line.includes('(ignored)')) {
88
+ async function loadAttwCore() {
89
+ const packageJson = fileURLToPath(import.meta.resolve('@arethetypeswrong/core/package.json'));
90
+ const core = await import(pathToFileURL(join(dirname(packageJson), 'dist', 'index.js')).href);
91
+ if (!isRecord(core)) {
92
+ throw new Error('@arethetypeswrong/core does not expose the expected API');
93
+ }
94
+ const PackageConstructor = core.Package;
95
+ const checkPackage = core.checkPackage;
96
+ if (typeof PackageConstructor !== 'function' || typeof checkPackage !== 'function') {
97
+ throw new Error('@arethetypeswrong/core does not expose the expected API');
98
+ }
99
+ return {
100
+ createPackage: (files, packageName, packageVersion) => Reflect.construct(PackageConstructor, [files, packageName, packageVersion]),
101
+ checkPackage: (pkg, options) => Promise.resolve(checkPackage(pkg, options)),
102
+ };
103
+ }
104
+ async function createAttwPackageFromTarball(attw, root, tarballPath) {
105
+ const temp = mkdtempSync(join(tmpdir(), 'smoo-attw-'));
106
+ try {
107
+ const extract = await runResult('tar', ['-xzf', tarballPath, '-C', temp], root);
108
+ if (extract.exitCode !== 0) {
109
+ printCommandOutput(extract.stdout, extract.stderr);
110
+ throw new Error('unable to extract packed package for are-the-types-wrong');
111
+ }
112
+ return createAttwPackageFromDirectory(attw, join(temp, 'package'));
113
+ }
114
+ finally {
115
+ rmSync(temp, { recursive: true, force: true });
116
+ }
117
+ }
118
+ function createAttwPackageFromDirectory(attw, packageDir) {
119
+ const packageJson = readJsonFile(join(packageDir, 'package.json'));
120
+ const packageName = typeof packageJson.name === 'string' ? packageJson.name : null;
121
+ const packageVersion = typeof packageJson.version === 'string' ? packageJson.version : null;
122
+ if (!packageName || !packageVersion) {
123
+ throw new Error('packed package.json must contain name and version');
124
+ }
125
+ const files = {};
126
+ collectAttwFiles(packageDir, packageDir, packageName, files);
127
+ return attw.createPackage(files, packageName, packageVersion);
128
+ }
129
+ function collectAttwFiles(root, current, packageName, files) {
130
+ for (const entry of readdirSync(current)) {
131
+ const path = join(current, entry);
132
+ const stat = statSync(path);
133
+ if (stat.isDirectory()) {
134
+ collectAttwFiles(root, path, packageName, files);
91
135
  continue;
92
136
  }
93
- console.error(line);
137
+ if (!stat.isFile()) {
138
+ continue;
139
+ }
140
+ const packageRelativePath = relative(root, path).split('\\').join('/');
141
+ files[`/node_modules/${packageName}/${packageRelativePath}`] = new Uint8Array(readFileSync(path));
142
+ }
143
+ }
144
+ function readJsonFile(path) {
145
+ const parsed = JSON.parse(readFileSync(path, 'utf8'));
146
+ if (!isRecord(parsed)) {
147
+ throw new Error(`${path} is not a JSON object`);
148
+ }
149
+ return parsed;
150
+ }
151
+ function isReportedAttwProblem(problem) {
152
+ if (!isRecord(problem) || typeof problem.kind !== 'string') {
153
+ return false;
154
+ }
155
+ const flag = attwProblemFlag(problem.kind);
156
+ if (flag === 'cjs-resolves-to-esm') {
157
+ return false;
94
158
  }
159
+ return problem.resolutionKind !== 'node10';
95
160
  }
96
- function attwExcludedEntrypointArgs(pkg) {
97
- const excluded = wasmExportEntrypoints(pkg.json.exports);
98
- return excluded.length === 0 ? [] : ['--exclude-entrypoints', ...excluded];
161
+ function attwProblemFlag(kind) {
162
+ switch (kind) {
163
+ case 'NoResolution':
164
+ return 'no-resolution';
165
+ case 'UntypedResolution':
166
+ return 'untyped-resolution';
167
+ case 'FalseCJS':
168
+ return 'false-cjs';
169
+ case 'FalseESM':
170
+ return 'false-esm';
171
+ case 'CJSResolvesToESM':
172
+ return 'cjs-resolves-to-esm';
173
+ case 'FallbackCondition':
174
+ return 'fallback-condition';
175
+ case 'CJSOnlyExportsDefault':
176
+ return 'cjs-only-exports-default';
177
+ case 'NamedExports':
178
+ return 'named-exports';
179
+ case 'FalseExportDefault':
180
+ return 'false-export-default';
181
+ case 'MissingExportEquals':
182
+ return 'missing-export-equals';
183
+ case 'UnexpectedModuleSyntax':
184
+ return 'unexpected-module-syntax';
185
+ case 'InternalResolutionError':
186
+ return 'internal-resolution-error';
187
+ default:
188
+ return null;
189
+ }
190
+ }
191
+ function formatProblemLocation(problem) {
192
+ if (!isRecord(problem)) {
193
+ return '';
194
+ }
195
+ const entrypoint = typeof problem.entrypoint === 'string' ? ` ${problem.entrypoint}` : '';
196
+ const resolution = typeof problem.resolutionKind === 'string' ? ` ${formatResolutionKind(problem.resolutionKind)}` : '';
197
+ return `${entrypoint}${resolution}`;
198
+ }
199
+ function formatResolutionKind(kind) {
200
+ if (kind === 'node16-cjs') {
201
+ return 'node16 (from CJS)';
202
+ }
203
+ if (kind === 'node16-esm') {
204
+ return 'node16 (from ESM)';
205
+ }
206
+ return kind;
99
207
  }
100
208
  function wasmExportEntrypoints(exports) {
101
209
  if (!isRecord(exports)) {
@@ -296,6 +296,8 @@ async function readResolvedTargetsByProject(ctx) {
296
296
  {
297
297
  targets: new Set(project.targets),
298
298
  ...(project.buildDependsOn ? { buildDependsOn: project.buildDependsOn } : {}),
299
+ ...(project.targetExecutors ? { targetExecutors: project.targetExecutors } : {}),
300
+ ...(project.targetScripts ? { targetScripts: project.targetScripts } : {}),
299
301
  },
300
302
  ]));
301
303
  }
@@ -2,6 +2,8 @@ export interface ProjectTargets {
2
2
  project: string;
3
3
  targets: string[];
4
4
  buildDependsOn?: string[];
5
+ targetExecutors?: Map<string, string>;
6
+ targetScripts?: Map<string, string>;
5
7
  }
6
8
  export interface CommandInvocation {
7
9
  command: string;
@@ -12,6 +14,8 @@ export declare function nxShowProjectCommand(project: string): CommandInvocation
12
14
  export declare function nxCacheDirectories(root: string): string[];
13
15
  export declare function targetNamesFromNxProjectJson(value: unknown): string[];
14
16
  export declare function buildDependsOnFromNxProjectJson(value: unknown): string[] | undefined;
17
+ export declare function targetExecutorsFromNxProjectJson(value: unknown): Map<string, string>;
18
+ export declare function targetScriptsFromNxProjectJson(value: unknown): Map<string, string>;
15
19
  export declare function formatProjectTargetLines(projects: ProjectTargets[]): string;
16
20
  export declare function projectNamesWithTarget(projects: ProjectTargets[], target: string): string[];
17
21
  export declare function projectNamesFromNxShowProjectsOutput(output: string): string[];
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/nx/index.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;CAC3B;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,EAAE,CAAC;CAChB;AAED,wBAAgB,cAAc,IAAI,iBAAiB,CAElD;AAED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,iBAAiB,CAEvE;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAEzD;AAED,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,EAAE,CAGrE;AAED,wBAAgB,+BAA+B,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,EAAE,GAAG,SAAS,CAOpF;AAED,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,cAAc,EAAE,GAAG,MAAM,CAK3E;AAED,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,cAAc,EAAE,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAK3F;AAED,wBAAgB,oCAAoC,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAsB7E;AAED,wBAAsB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAK7D;AAED,wBAAsB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE;IAAE,UAAU,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAQhG;AAED,wBAAsB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAG5D;AAED,wBAAsB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAa5D;AAED,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC,CAGhF"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/nx/index.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,eAAe,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,aAAa,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACrC;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,EAAE,CAAC;CAChB;AAED,wBAAgB,cAAc,IAAI,iBAAiB,CAElD;AAED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,iBAAiB,CAEvE;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAEzD;AAED,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,EAAE,CAGrE;AAED,wBAAgB,+BAA+B,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,EAAE,GAAG,SAAS,CAOpF;AAED,wBAAgB,gCAAgC,CAAC,KAAK,EAAE,OAAO,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAYpF;AAED,wBAAgB,8BAA8B,CAAC,KAAK,EAAE,OAAO,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAgBlF;AAED,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,cAAc,EAAE,GAAG,MAAM,CAK3E;AAED,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,cAAc,EAAE,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAK3F;AAED,wBAAgB,oCAAoC,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAsB7E;AAED,wBAAsB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAK7D;AAED,wBAAsB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE;IAAE,UAAU,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAQhG;AAED,wBAAsB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAG5D;AAED,wBAAsB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAa5D;AAED,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC,CAGhF"}
package/dist/nx/index.js CHANGED
@@ -25,6 +25,36 @@ export function buildDependsOnFromNxProjectJson(value) {
25
25
  }
26
26
  return build.dependsOn;
27
27
  }
28
+ export function targetExecutorsFromNxProjectJson(value) {
29
+ const targets = isRecord(value) ? recordProperty(value, 'targets') : null;
30
+ const executors = new Map();
31
+ if (!targets) {
32
+ return executors;
33
+ }
34
+ for (const [targetName, target] of Object.entries(targets)) {
35
+ if (isRecord(target) && typeof target.executor === 'string') {
36
+ executors.set(targetName, target.executor);
37
+ }
38
+ }
39
+ return executors;
40
+ }
41
+ export function targetScriptsFromNxProjectJson(value) {
42
+ const targets = isRecord(value) ? recordProperty(value, 'targets') : null;
43
+ const scripts = new Map();
44
+ if (!targets) {
45
+ return scripts;
46
+ }
47
+ for (const [targetName, target] of Object.entries(targets)) {
48
+ if (!isRecord(target)) {
49
+ continue;
50
+ }
51
+ const options = recordProperty(target, 'options');
52
+ if (typeof options?.script === 'string') {
53
+ scripts.set(targetName, options.script);
54
+ }
55
+ }
56
+ return scripts;
57
+ }
28
58
  export function formatProjectTargetLines(projects) {
29
59
  return projects
30
60
  .flatMap((project) => project.targets.map((target) => `${project.project}:${target}`))
@@ -108,5 +138,7 @@ async function readProjectTarget(root, project) {
108
138
  project,
109
139
  targets: targetNamesFromNxProjectJson(parsed),
110
140
  buildDependsOn: buildDependsOnFromNxProjectJson(parsed),
141
+ targetExecutors: targetExecutorsFromNxProjectJson(parsed),
142
+ targetScripts: targetScriptsFromNxProjectJson(parsed),
111
143
  };
112
144
  }
@@ -0,0 +1,18 @@
1
+ # Worktrees inherit the real project .envrc once, then stop. The same file is
2
+ # checked out inside .jcode-worktrees, so continuing after source_up_if_exists
3
+ # would run the devenv setup twice while sandbox blocks access to tooling/.
4
+ case "$PWD" in
5
+ */.jcode-worktrees/*)
6
+ ;;
7
+ *)
8
+ source_up_if_exists
9
+ ;;
10
+ esac
11
+
12
+ watch_file tooling/direnv/envrc.sh
13
+
14
+ cd tooling/direnv
15
+ . envrc.sh
16
+
17
+ # Load local extensions and overrides
18
+ source_env_if_exists .envrc-local
@@ -0,0 +1,131 @@
1
+ #!/usr/bin/env bun
2
+ import { existsSync } from 'node:fs';
3
+ import { mkdir, rmdir } from 'node:fs/promises';
4
+ import path from 'node:path';
5
+ import { $ } from 'bun';
6
+
7
+ const devenvRoot = process.env.DEVENV_ROOT;
8
+ const projectRoot = path.resolve(`${devenvRoot}/../..`);
9
+ const startedWithoutNodeModules = !existsSync(path.join(projectRoot, 'node_modules'));
10
+
11
+ class CapturedCommandError extends Error {
12
+ constructor(
13
+ public readonly command: string,
14
+ public readonly exitCode: number,
15
+ public readonly stdout: Uint8Array,
16
+ public readonly stderr: Uint8Array,
17
+ ) {
18
+ super(`${command} failed with exit code ${exitCode}`);
19
+ }
20
+ }
21
+
22
+ // Go to project root
23
+ process.chdir(projectRoot);
24
+
25
+ try {
26
+ // Install dependencies first so node_modules/.bin tools are available
27
+ if (process.env.CI) {
28
+ try {
29
+ await runSetupCommand('bun install --frozen-lockfile', $`bun install --frozen-lockfile`, { quiet: false });
30
+ } catch (error) {
31
+ console.error('! Failed to install dependencies with frozen lockfile');
32
+ replayCapturedOutput(error);
33
+ await runSetupCommand('bun install', $`bun install`, { quiet: false });
34
+ console.error('git diff after install:');
35
+ await runSetupCommand('git diff', $`git diff`, { quiet: false, allowNonzero: true });
36
+ process.exit(1);
37
+ }
38
+ } else {
39
+ await installLocalDependencies();
40
+ }
41
+
42
+ // Bun selects its resolver mode at process startup. If this process started
43
+ // before node_modules existed, imports below can stay in auto-install mode
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
+ }
54
+
55
+ const { applyWorkspaceGitConfig } = await import('@smoothbricks/cli/monorepo/git-config');
56
+ await applyWorkspaceGitConfig(projectRoot);
57
+ } catch (error) {
58
+ if (error instanceof CapturedCommandError) {
59
+ console.error(`--- ERROR: setup-environment.ts failed while running: ${error.command}`);
60
+ console.error(`exit code: ${error.exitCode}`);
61
+ } else {
62
+ console.error(`--- ERROR: setup-environment.ts failed: ${error}`);
63
+ }
64
+ replayCapturedOutput(error);
65
+ console.error('\n---');
66
+ process.exit(1);
67
+ }
68
+
69
+ async function installLocalDependencies(): Promise<void> {
70
+ // bun install runs the root prepare script, which patches TypeScript with
71
+ // ts-patch. Multiple concurrent direnv activations can otherwise race while
72
+ // mutating the same files under node_modules.
73
+ await withSetupLock(async () => {
74
+ await runSetupCommand('bun install --no-summary', $`bun install --no-summary`);
75
+ });
76
+ }
77
+
78
+ async function runSetupCommand(
79
+ command: string,
80
+ shell: ReturnType<typeof $>,
81
+ options: { quiet?: boolean; allowNonzero?: boolean } = {},
82
+ ): Promise<void> {
83
+ const result = await shell
84
+ .quiet(options.quiet ?? true)
85
+ .nothrow()
86
+ .cwd(projectRoot);
87
+ if (result.exitCode !== 0 && options.allowNonzero !== true) {
88
+ throw new CapturedCommandError(command, result.exitCode, result.stdout, result.stderr);
89
+ }
90
+ }
91
+
92
+ async function withSetupLock<T>(fn: () => Promise<T>): Promise<T> {
93
+ const lockDir = path.join(projectRoot, 'tooling/direnv/.devenv/setup-environment.lock');
94
+ await acquireLock(lockDir);
95
+ try {
96
+ return await fn();
97
+ } finally {
98
+ await rmdir(lockDir).catch(() => undefined);
99
+ }
100
+ }
101
+
102
+ async function acquireLock(lockDir: string): Promise<void> {
103
+ const deadline = Date.now() + 120_000;
104
+ while (true) {
105
+ try {
106
+ await mkdir(lockDir, { recursive: false });
107
+ return;
108
+ } catch (error) {
109
+ if (!isFileExistsError(error) || Date.now() > deadline) {
110
+ throw error;
111
+ }
112
+ await Bun.sleep(100);
113
+ }
114
+ }
115
+ }
116
+
117
+ function isFileExistsError(error: unknown): boolean {
118
+ return error instanceof Error && 'code' in error && error.code === 'EEXIST';
119
+ }
120
+
121
+ function replayCapturedOutput(error: unknown): void {
122
+ if (!(error instanceof CapturedCommandError)) {
123
+ return;
124
+ }
125
+ if (error.stdout.length > 0) {
126
+ process.stdout.write(error.stdout);
127
+ }
128
+ if (error.stderr.length > 0) {
129
+ process.stderr.write(error.stderr);
130
+ }
131
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@smoothbricks/cli",
3
- "version": "0.4.1",
3
+ "version": "0.4.3",
4
4
  "type": "module",
5
5
  "description": "SmoothBricks monorepo automation CLI",
6
6
  "bin": {
@@ -44,8 +44,8 @@
44
44
  "!**/*.tsbuildinfo"
45
45
  ],
46
46
  "dependencies": {
47
- "@arethetypeswrong/cli": "^0.18.2",
48
- "@smoothbricks/nx-plugin": "0.2.4",
47
+ "@arethetypeswrong/core": "^0.18.2",
48
+ "@smoothbricks/nx-plugin": "0.2.5",
49
49
  "@smoothbricks/validation": "0.1.1",
50
50
  "commander": "^14.0.3",
51
51
  "publint": "^0.3.18",
package/src/lib/run.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { spawn } from 'node:child_process';
2
2
  import { existsSync } from 'node:fs';
3
- import { dirname, join } from 'node:path';
3
+ import { join } from 'node:path';
4
4
  import { fileURLToPath } from 'node:url';
5
5
  import { $ } from 'bun';
6
6
 
@@ -106,10 +106,6 @@ function resolveBundledCommand(command: string): string | null {
106
106
  if (command === 'sherif') {
107
107
  return fileURLToPath(import.meta.resolve('sherif'));
108
108
  }
109
- if (command === 'attw') {
110
- const packageJson = fileURLToPath(import.meta.resolve('@arethetypeswrong/cli/package.json'));
111
- return join(dirname(packageJson), 'dist', 'index.js');
112
- }
113
109
  } catch {
114
110
  return null;
115
111
  }
@@ -38,6 +38,11 @@ interface DeployTargetInfo {
38
38
  }
39
39
 
40
40
  const managedFiles: ManagedFile[] = [
41
+ {
42
+ kind: 'raw',
43
+ source: 'envrc',
44
+ target: '.envrc',
45
+ },
41
46
  {
42
47
  kind: 'raw',
43
48
  source: 'tooling/direnv/repo-path',
@@ -50,6 +55,12 @@ const managedFiles: ManagedFile[] = [
50
55
  target: 'tooling/direnv/github-actions-bootstrap.sh',
51
56
  executable: true,
52
57
  },
58
+ {
59
+ kind: 'raw',
60
+ source: 'tooling/direnv/setup-environment.ts',
61
+ target: 'tooling/direnv/setup-environment.ts',
62
+ executable: true,
63
+ },
53
64
  {
54
65
  kind: 'raw',
55
66
  source: 'tooling/git-hooks/pre-commit.sh',
@@ -1,5 +1,7 @@
1
- import { readFileSync, rmSync, unlinkSync } from 'node:fs';
2
- import { join } from 'node:path';
1
+ import { mkdtempSync, readdirSync, readFileSync, rmSync, statSync, unlinkSync } from 'node:fs';
2
+ import { tmpdir } from 'node:os';
3
+ import { dirname, join, relative } from 'node:path';
4
+ import { fileURLToPath, pathToFileURL } from 'node:url';
3
5
  import { publint } from 'publint';
4
6
  import { formatMessage } from 'publint/utils';
5
7
  import { isRecord } from '../lib/json.js';
@@ -86,39 +88,158 @@ async function validatePackedManifest(
86
88
  }
87
89
 
88
90
  async function validateAttw(root: string, pkg: PackageInfo, packed: { path: string }): Promise<number> {
89
- const attwArgs = [
90
- packed.path,
91
- '--format',
92
- 'ascii',
93
- '--no-color',
94
- '--profile',
95
- 'node16',
96
- '--ignore-rules',
97
- 'cjs-resolves-to-esm',
98
- ...attwExcludedEntrypointArgs(pkg),
99
- ];
100
- const attw = await runResult('attw', attwArgs, root);
101
- if (attw.exitCode === 0) {
91
+ const attw = await loadAttwCore();
92
+ const analysis = await attw.checkPackage(await createAttwPackageFromTarball(attw, root, packed.path), {
93
+ excludeEntrypoints: wasmExportEntrypoints(pkg.json.exports),
94
+ });
95
+ if (!isRecord(analysis) || analysis.types === false) {
102
96
  return 0;
103
97
  }
104
- printAttwOutput(attw.stdout);
105
- printAttwOutput(attw.stderr);
98
+ const rawProblems = Array.isArray(analysis.problems) ? analysis.problems : [];
99
+ const problems = rawProblems.filter(isReportedAttwProblem);
100
+ if (problems.length === 0) {
101
+ return 0;
102
+ }
103
+ for (const problem of problems) {
104
+ console.error(`${pkg.path}: are-the-types-wrong ${problem.kind}${formatProblemLocation(problem)}`);
105
+ }
106
106
  console.error(`${pkg.path}: are-the-types-wrong validation failed`);
107
107
  return 1;
108
108
  }
109
109
 
110
- function printAttwOutput(output: string): void {
111
- for (const line of output.split('\n')) {
112
- if (!line || line.includes('(ignored)')) {
110
+ interface AttwCore {
111
+ createPackage: (files: Record<string, Uint8Array>, packageName: string, packageVersion: string) => unknown;
112
+ checkPackage: (pkg: unknown, options: { excludeEntrypoints: string[] }) => Promise<unknown>;
113
+ }
114
+
115
+ async function loadAttwCore(): Promise<AttwCore> {
116
+ const packageJson = fileURLToPath(import.meta.resolve('@arethetypeswrong/core/package.json'));
117
+ const core = await import(pathToFileURL(join(dirname(packageJson), 'dist', 'index.js')).href);
118
+ if (!isRecord(core)) {
119
+ throw new Error('@arethetypeswrong/core does not expose the expected API');
120
+ }
121
+ const PackageConstructor = core.Package;
122
+ const checkPackage = core.checkPackage;
123
+ if (typeof PackageConstructor !== 'function' || typeof checkPackage !== 'function') {
124
+ throw new Error('@arethetypeswrong/core does not expose the expected API');
125
+ }
126
+ return {
127
+ createPackage: (files, packageName, packageVersion) =>
128
+ Reflect.construct(PackageConstructor, [files, packageName, packageVersion]),
129
+ checkPackage: (pkg, options) => Promise.resolve(checkPackage(pkg, options)),
130
+ };
131
+ }
132
+
133
+ async function createAttwPackageFromTarball(attw: AttwCore, root: string, tarballPath: string): Promise<unknown> {
134
+ const temp = mkdtempSync(join(tmpdir(), 'smoo-attw-'));
135
+ try {
136
+ const extract = await runResult('tar', ['-xzf', tarballPath, '-C', temp], root);
137
+ if (extract.exitCode !== 0) {
138
+ printCommandOutput(extract.stdout, extract.stderr);
139
+ throw new Error('unable to extract packed package for are-the-types-wrong');
140
+ }
141
+ return createAttwPackageFromDirectory(attw, join(temp, 'package'));
142
+ } finally {
143
+ rmSync(temp, { recursive: true, force: true });
144
+ }
145
+ }
146
+
147
+ function createAttwPackageFromDirectory(attw: AttwCore, packageDir: string): unknown {
148
+ const packageJson = readJsonFile(join(packageDir, 'package.json'));
149
+ const packageName = typeof packageJson.name === 'string' ? packageJson.name : null;
150
+ const packageVersion = typeof packageJson.version === 'string' ? packageJson.version : null;
151
+ if (!packageName || !packageVersion) {
152
+ throw new Error('packed package.json must contain name and version');
153
+ }
154
+ const files: Record<string, Uint8Array> = {};
155
+ collectAttwFiles(packageDir, packageDir, packageName, files);
156
+ return attw.createPackage(files, packageName, packageVersion);
157
+ }
158
+
159
+ function collectAttwFiles(root: string, current: string, packageName: string, files: Record<string, Uint8Array>): void {
160
+ for (const entry of readdirSync(current)) {
161
+ const path = join(current, entry);
162
+ const stat = statSync(path);
163
+ if (stat.isDirectory()) {
164
+ collectAttwFiles(root, path, packageName, files);
113
165
  continue;
114
166
  }
115
- console.error(line);
167
+ if (!stat.isFile()) {
168
+ continue;
169
+ }
170
+ const packageRelativePath = relative(root, path).split('\\').join('/');
171
+ files[`/node_modules/${packageName}/${packageRelativePath}`] = new Uint8Array(readFileSync(path));
172
+ }
173
+ }
174
+
175
+ function readJsonFile(path: string): Record<string, unknown> {
176
+ const parsed: unknown = JSON.parse(readFileSync(path, 'utf8'));
177
+ if (!isRecord(parsed)) {
178
+ throw new Error(`${path} is not a JSON object`);
179
+ }
180
+ return parsed;
181
+ }
182
+
183
+ function isReportedAttwProblem(problem: unknown): boolean {
184
+ if (!isRecord(problem) || typeof problem.kind !== 'string') {
185
+ return false;
186
+ }
187
+ const flag = attwProblemFlag(problem.kind);
188
+ if (flag === 'cjs-resolves-to-esm') {
189
+ return false;
116
190
  }
191
+ return problem.resolutionKind !== 'node10';
117
192
  }
118
193
 
119
- function attwExcludedEntrypointArgs(pkg: PackageInfo): string[] {
120
- const excluded = wasmExportEntrypoints(pkg.json.exports);
121
- return excluded.length === 0 ? [] : ['--exclude-entrypoints', ...excluded];
194
+ function attwProblemFlag(kind: string): string | null {
195
+ switch (kind) {
196
+ case 'NoResolution':
197
+ return 'no-resolution';
198
+ case 'UntypedResolution':
199
+ return 'untyped-resolution';
200
+ case 'FalseCJS':
201
+ return 'false-cjs';
202
+ case 'FalseESM':
203
+ return 'false-esm';
204
+ case 'CJSResolvesToESM':
205
+ return 'cjs-resolves-to-esm';
206
+ case 'FallbackCondition':
207
+ return 'fallback-condition';
208
+ case 'CJSOnlyExportsDefault':
209
+ return 'cjs-only-exports-default';
210
+ case 'NamedExports':
211
+ return 'named-exports';
212
+ case 'FalseExportDefault':
213
+ return 'false-export-default';
214
+ case 'MissingExportEquals':
215
+ return 'missing-export-equals';
216
+ case 'UnexpectedModuleSyntax':
217
+ return 'unexpected-module-syntax';
218
+ case 'InternalResolutionError':
219
+ return 'internal-resolution-error';
220
+ default:
221
+ return null;
222
+ }
223
+ }
224
+
225
+ function formatProblemLocation(problem: unknown): string {
226
+ if (!isRecord(problem)) {
227
+ return '';
228
+ }
229
+ const entrypoint = typeof problem.entrypoint === 'string' ? ` ${problem.entrypoint}` : '';
230
+ const resolution =
231
+ typeof problem.resolutionKind === 'string' ? ` ${formatResolutionKind(problem.resolutionKind)}` : '';
232
+ return `${entrypoint}${resolution}`;
233
+ }
234
+
235
+ function formatResolutionKind(kind: string): string {
236
+ if (kind === 'node16-cjs') {
237
+ return 'node16 (from CJS)';
238
+ }
239
+ if (kind === 'node16-esm') {
240
+ return 'node16 (from ESM)';
241
+ }
242
+ return kind;
122
243
  }
123
244
 
124
245
  function wasmExportEntrypoints(exports: unknown): string[] {
@@ -395,6 +395,8 @@ async function readResolvedTargetsByProject(ctx: MonorepoContext): Promise<Map<s
395
395
  {
396
396
  targets: new Set(project.targets),
397
397
  ...(project.buildDependsOn ? { buildDependsOn: project.buildDependsOn } : {}),
398
+ ...(project.targetExecutors ? { targetExecutors: project.targetExecutors } : {}),
399
+ ...(project.targetScripts ? { targetScripts: project.targetScripts } : {}),
398
400
  },
399
401
  ]),
400
402
  );
package/src/nx/index.ts CHANGED
@@ -9,6 +9,8 @@ export interface ProjectTargets {
9
9
  project: string;
10
10
  targets: string[];
11
11
  buildDependsOn?: string[];
12
+ targetExecutors?: Map<string, string>;
13
+ targetScripts?: Map<string, string>;
12
14
  }
13
15
 
14
16
  export interface CommandInvocation {
@@ -42,6 +44,38 @@ export function buildDependsOnFromNxProjectJson(value: unknown): string[] | unde
42
44
  return build.dependsOn;
43
45
  }
44
46
 
47
+ export function targetExecutorsFromNxProjectJson(value: unknown): Map<string, string> {
48
+ const targets = isRecord(value) ? recordProperty(value, 'targets') : null;
49
+ const executors = new Map<string, string>();
50
+ if (!targets) {
51
+ return executors;
52
+ }
53
+ for (const [targetName, target] of Object.entries(targets)) {
54
+ if (isRecord(target) && typeof target.executor === 'string') {
55
+ executors.set(targetName, target.executor);
56
+ }
57
+ }
58
+ return executors;
59
+ }
60
+
61
+ export function targetScriptsFromNxProjectJson(value: unknown): Map<string, string> {
62
+ const targets = isRecord(value) ? recordProperty(value, 'targets') : null;
63
+ const scripts = new Map<string, string>();
64
+ if (!targets) {
65
+ return scripts;
66
+ }
67
+ for (const [targetName, target] of Object.entries(targets)) {
68
+ if (!isRecord(target)) {
69
+ continue;
70
+ }
71
+ const options = recordProperty(target, 'options');
72
+ if (typeof options?.script === 'string') {
73
+ scripts.set(targetName, options.script);
74
+ }
75
+ }
76
+ return scripts;
77
+ }
78
+
45
79
  export function formatProjectTargetLines(projects: ProjectTargets[]): string {
46
80
  return projects
47
81
  .flatMap((project) => project.targets.map((target) => `${project.project}:${target}`))
@@ -135,5 +169,7 @@ async function readProjectTarget(root: string, project: string): Promise<Project
135
169
  project,
136
170
  targets: targetNamesFromNxProjectJson(parsed),
137
171
  buildDependsOn: buildDependsOnFromNxProjectJson(parsed),
172
+ targetExecutors: targetExecutorsFromNxProjectJson(parsed),
173
+ targetScripts: targetScriptsFromNxProjectJson(parsed),
138
174
  };
139
175
  }