@t3x-dev/local 0.1.2

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.
@@ -0,0 +1,121 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from 'node:fs/promises';
4
+ import path from 'node:path';
5
+ import { FIXED_VERSION_PACKAGES, verifyVersionsOrThrow } from '../../../tools/verify-versions.mjs';
6
+ import {
7
+ getLocalPackageDir,
8
+ getRuntimeArtifactsDir,
9
+ getRuntimeManifestPath,
10
+ readJson,
11
+ sha256File,
12
+ statSize,
13
+ writeJson,
14
+ } from './runtime-helpers.mjs';
15
+
16
+ const packageDir = getLocalPackageDir();
17
+ const repoRoot = path.resolve(packageDir, '../..');
18
+ await verifyVersionsOrThrow({ repoRoot, verifyManifest: false });
19
+ const localPackageJson = await readJson(path.join(packageDir, 'package.json'));
20
+ const webPackageJson = await readJson(path.join(repoRoot, 'apps', 'web', 'package.json'));
21
+ const artifactsDir = path.resolve(
22
+ process.env.T3X_LOCAL_RUNTIME_OUTPUT_DIR ?? getRuntimeArtifactsDir(packageDir)
23
+ );
24
+ const manifestPath = getRuntimeManifestPath(packageDir);
25
+ const releaseBaseUrl =
26
+ process.env.T3X_LOCAL_RUNTIME_BASE_URL ??
27
+ `https://github.com/t3x-dev/t3x-core/releases/download/t3x-local-v${localPackageJson.version}`;
28
+ const versions = await readFixedPackageVersions(repoRoot);
29
+ const runtimeArtifacts = await collectRuntimeArtifacts(artifactsDir, localPackageJson.version);
30
+
31
+ const manifest = {
32
+ manifestVersion: 1,
33
+ packageVersion: localPackageJson.version,
34
+ fixedVersion: localPackageJson.version,
35
+ generatedAt: new Date().toISOString(),
36
+ dependencies: {
37
+ ...versions,
38
+ web: webPackageJson.version,
39
+ },
40
+ platforms: runtimeArtifacts.reduce((accumulator, artifact) => {
41
+ accumulator[artifact.platformKey] = {
42
+ fileName: artifact.fileName,
43
+ url: `${releaseBaseUrl.replace(/\/$/, '')}/${artifact.fileName}`,
44
+ sha256: artifact.sha256,
45
+ size: artifact.size,
46
+ };
47
+ return accumulator;
48
+ }, {}),
49
+ };
50
+
51
+ await writeJson(manifestPath, manifest);
52
+ console.log(`[generate-runtime-manifest] Wrote ${manifestPath}`);
53
+
54
+ async function readFixedPackageVersions(rootDir) {
55
+ const versionMap = {};
56
+
57
+ for (const packageName of FIXED_VERSION_PACKAGES) {
58
+ versionMap[packageName] = (
59
+ await readJson(path.join(rootDir, getWorkspacePackagePath(packageName), 'package.json'))
60
+ ).version;
61
+ }
62
+
63
+ return versionMap;
64
+ }
65
+
66
+ async function collectRuntimeArtifacts(artifactDir, packageVersion) {
67
+ const entries = await fs.readdir(artifactDir, { withFileTypes: true });
68
+ const prefix = `t3x-local-runtime-${packageVersion}-`;
69
+ const suffix = '.tar.gz';
70
+ const artifacts = [];
71
+
72
+ for (const entry of entries) {
73
+ if (!entry.isFile() || !entry.name.startsWith(prefix) || !entry.name.endsWith(suffix)) {
74
+ continue;
75
+ }
76
+
77
+ const platformKey = entry.name.slice(prefix.length, -suffix.length);
78
+ const artifactPath = path.join(artifactDir, entry.name);
79
+
80
+ artifacts.push({
81
+ platformKey,
82
+ fileName: entry.name,
83
+ sha256: await sha256File(artifactPath),
84
+ size: await statSize(artifactPath),
85
+ });
86
+ }
87
+
88
+ if (artifacts.length === 0) {
89
+ throw new Error(
90
+ `No runtime artifacts found in ${artifactDir} for @t3x-dev/local ${packageVersion}`
91
+ );
92
+ }
93
+
94
+ artifacts.sort((left, right) => left.platformKey.localeCompare(right.platformKey));
95
+ return artifacts;
96
+ }
97
+
98
+ function getWorkspacePackagePath(packageName) {
99
+ switch (packageName) {
100
+ case '@t3x-dev/yops':
101
+ return path.join('packages', 'yops');
102
+ case '@t3x-dev/yschema':
103
+ return path.join('packages', 'yschema');
104
+ case '@t3x-dev/core':
105
+ return path.join('packages', 'core');
106
+ case '@t3x-dev/storage':
107
+ return path.join('packages', 'storage');
108
+ case '@t3x-dev/api':
109
+ return path.join('packages', 'api');
110
+ case '@t3x-dev/api-client':
111
+ return path.join('packages', 'api-client');
112
+ case '@t3x-dev/cli':
113
+ return path.join('apps', 'cli');
114
+ case '@t3x-dev/mcp':
115
+ return path.join('apps', 'mcp');
116
+ case '@t3x-dev/local':
117
+ return path.join('apps', 'local');
118
+ default:
119
+ throw new Error(`Unsupported fixed package ${packageName}`);
120
+ }
121
+ }
@@ -0,0 +1,209 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { spawnSync } from 'node:child_process';
4
+ import crypto from 'node:crypto';
5
+ import fs from 'node:fs/promises';
6
+ import os from 'node:os';
7
+ import path from 'node:path';
8
+ import {
9
+ assertRuntimeLayout,
10
+ ensureDir,
11
+ fileExists,
12
+ findWorkspaceRepoRoot,
13
+ getDefaultInstalledRuntimeDir,
14
+ getLocalPackageDir,
15
+ getPlatformKey,
16
+ getRuntimeManifestPath,
17
+ readJson,
18
+ resolveMirrorArtifactLocation,
19
+ } from './runtime-helpers.mjs';
20
+
21
+ const FIXED_VERSION_PACKAGES = [
22
+ '@t3x-dev/yops',
23
+ '@t3x-dev/yschema',
24
+ '@t3x-dev/core',
25
+ '@t3x-dev/storage',
26
+ '@t3x-dev/api',
27
+ '@t3x-dev/api-client',
28
+ '@t3x-dev/cli',
29
+ '@t3x-dev/mcp',
30
+ '@t3x-dev/local',
31
+ ];
32
+ const LOCAL_DIRECT_FIXED_DEPENDENCIES = [
33
+ '@t3x-dev/api',
34
+ '@t3x-dev/cli',
35
+ '@t3x-dev/mcp',
36
+ '@t3x-dev/storage',
37
+ ];
38
+
39
+ if (process.env.T3X_LOCAL_SKIP_DOWNLOAD === '1' || process.env.T3X_LOCAL_SKIP_DOWNLOAD === 'true') {
40
+ console.log(
41
+ '[t3x-local:postinstall] Skipping runtime download because T3X_LOCAL_SKIP_DOWNLOAD is set.'
42
+ );
43
+ process.exit(0);
44
+ }
45
+
46
+ const packageDir = getLocalPackageDir();
47
+ const manifestPath = getRuntimeManifestPath(packageDir);
48
+ const workspaceRepoRoot = await findWorkspaceRepoRoot(packageDir);
49
+
50
+ if (workspaceRepoRoot) {
51
+ console.log(
52
+ `[t3x-local:postinstall] Detected workspace install at ${workspaceRepoRoot}. Skipping runtime download.`
53
+ );
54
+ process.exit(0);
55
+ }
56
+
57
+ if (!(await fileExists(manifestPath))) {
58
+ console.log('[t3x-local:postinstall] No runtime manifest found. Skipping runtime download.');
59
+ process.exit(0);
60
+ }
61
+
62
+ const manifest = await readJson(manifestPath);
63
+ await verifyInstalledVersionLock(packageDir, manifest);
64
+ const platformKey = getPlatformKey();
65
+ const artifact = manifest.platforms?.[platformKey];
66
+
67
+ if (!artifact) {
68
+ throw new Error(`[t3x-local:postinstall] No runtime artifact configured for ${platformKey}`);
69
+ }
70
+
71
+ const runtimeDir = path.resolve(
72
+ process.env.T3X_LOCAL_RUNTIME_DIR ?? getDefaultInstalledRuntimeDir(packageDir)
73
+ );
74
+ const downloadSource = process.env.T3X_LOCAL_RUNTIME_MIRROR
75
+ ? resolveMirrorArtifactLocation(process.env.T3X_LOCAL_RUNTIME_MIRROR, artifact.fileName)
76
+ : artifact.url;
77
+
78
+ if (!downloadSource) {
79
+ throw new Error(
80
+ '[t3x-local:postinstall] Runtime manifest did not contain a URL and no T3X_LOCAL_RUNTIME_MIRROR was provided.'
81
+ );
82
+ }
83
+
84
+ const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 't3x-local-postinstall-'));
85
+ const archivePath = path.join(tempDir, artifact.fileName);
86
+
87
+ try {
88
+ await downloadArtifact(downloadSource, archivePath);
89
+ await verifyArchiveSha(archivePath, artifact.sha256);
90
+
91
+ await fs.rm(runtimeDir, { recursive: true, force: true });
92
+ await ensureDir(runtimeDir);
93
+
94
+ const extractResult = spawnSync('tar', ['-xzf', archivePath, '-C', runtimeDir], {
95
+ stdio: 'inherit',
96
+ });
97
+
98
+ if (extractResult.status !== 0) {
99
+ throw new Error(
100
+ `[t3x-local:postinstall] tar extraction failed with exit code ${String(extractResult.status)}`
101
+ );
102
+ }
103
+
104
+ await assertRuntimeLayout(runtimeDir);
105
+ await ensurePackageNodeModulesLink(runtimeDir, packageDir);
106
+ await fs.writeFile(
107
+ path.join(runtimeDir, '.runtime-download.json'),
108
+ `${JSON.stringify(
109
+ {
110
+ packageVersion: manifest.packageVersion,
111
+ platform: platformKey,
112
+ source: downloadSource,
113
+ sha256: artifact.sha256,
114
+ installedAt: new Date().toISOString(),
115
+ },
116
+ null,
117
+ 2
118
+ )}\n`,
119
+ 'utf8'
120
+ );
121
+
122
+ console.log(`[t3x-local:postinstall] Runtime ready at ${runtimeDir}`);
123
+ } finally {
124
+ await fs.rm(tempDir, { recursive: true, force: true });
125
+ }
126
+
127
+ async function downloadArtifact(source, destinationPath) {
128
+ if (source.startsWith('file://')) {
129
+ await fs.copyFile(new URL(source), destinationPath);
130
+ return;
131
+ }
132
+
133
+ if (source.startsWith('http://') || source.startsWith('https://')) {
134
+ const response = await fetch(source);
135
+
136
+ if (!response.ok) {
137
+ throw new Error(
138
+ `[t3x-local:postinstall] Failed to download runtime: HTTP ${response.status}`
139
+ );
140
+ }
141
+
142
+ const arrayBuffer = await response.arrayBuffer();
143
+ await fs.writeFile(destinationPath, new Uint8Array(arrayBuffer));
144
+ return;
145
+ }
146
+
147
+ await fs.copyFile(source, destinationPath);
148
+ }
149
+
150
+ async function verifyArchiveSha(archivePath, expectedSha) {
151
+ const file = await fs.readFile(archivePath);
152
+ const actualSha = crypto.createHash('sha256').update(file).digest('hex');
153
+
154
+ if (actualSha !== expectedSha) {
155
+ throw new Error(
156
+ `[t3x-local:postinstall] SHA256 mismatch for runtime archive. Expected ${expectedSha}, got ${actualSha}`
157
+ );
158
+ }
159
+ }
160
+
161
+ async function ensurePackageNodeModulesLink(runtimeDir, packageDir) {
162
+ const runtimeNodeModulesPath = path.join(runtimeDir, 'node_modules');
163
+ const packageNodeModulesPath = path.join(packageDir, 'node_modules');
164
+
165
+ if (!(await fileExists(packageNodeModulesPath))) {
166
+ return;
167
+ }
168
+
169
+ await fs.rm(runtimeNodeModulesPath, { recursive: true, force: true });
170
+ await fs.symlink(packageNodeModulesPath, runtimeNodeModulesPath, 'junction');
171
+ }
172
+
173
+ async function verifyInstalledVersionLock(packageDir, manifest) {
174
+ const packageJson = await readJson(path.join(packageDir, 'package.json'));
175
+ const expectedVersion = packageJson.version;
176
+ const problems = [];
177
+
178
+ for (const dependencyName of LOCAL_DIRECT_FIXED_DEPENDENCIES) {
179
+ const actual = packageJson.dependencies?.[dependencyName];
180
+
181
+ if (actual !== expectedVersion && actual !== `workspace:${expectedVersion}`) {
182
+ problems.push(
183
+ `package.json dependency ${dependencyName} must pin ${expectedVersion}, found ${actual ?? 'missing'}`
184
+ );
185
+ }
186
+ }
187
+
188
+ if (manifest.packageVersion !== expectedVersion) {
189
+ problems.push(
190
+ `runtime-manifest.json packageVersion must be ${expectedVersion}, found ${manifest.packageVersion ?? 'missing'}`
191
+ );
192
+ }
193
+
194
+ for (const packageName of FIXED_VERSION_PACKAGES) {
195
+ const manifestVersion = manifest.dependencies?.[packageName];
196
+ if (manifestVersion !== expectedVersion) {
197
+ problems.push(
198
+ `runtime-manifest.json dependency ${packageName} must be ${expectedVersion}, found ${manifestVersion ?? 'missing'}`
199
+ );
200
+ }
201
+ }
202
+
203
+ if (problems.length > 0) {
204
+ throw new Error(
205
+ '[t3x-local:postinstall] Fixed version verification failed.\n' +
206
+ problems.map((problem) => `- ${problem}`).join('\n')
207
+ );
208
+ }
209
+ }
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from 'node:fs/promises';
4
+ import path from 'node:path';
5
+ import { fileExists, getLocalPackageDir } from './runtime-helpers.mjs';
6
+
7
+ const packageDir = getLocalPackageDir();
8
+ const packageJsonPath = path.join(packageDir, 'package.json');
9
+ const backupPath = path.join(packageDir, '.pack-package.json.backup');
10
+
11
+ if (!(await fileExists(backupPath))) {
12
+ console.log('[postpack] No package.json backup found. Nothing to restore.');
13
+ process.exit(0);
14
+ }
15
+
16
+ await fs.copyFile(backupPath, packageJsonPath);
17
+ await fs.rm(backupPath, { force: true });
18
+ console.log('[postpack] Restored apps/local/package.json');
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env node
2
+
3
+ import path from 'node:path';
4
+ import { getLocalPackageDir, readJson, writeJson } from './runtime-helpers.mjs';
5
+
6
+ const packageDir = getLocalPackageDir();
7
+ const repoRoot = path.resolve(packageDir, '../..');
8
+ const backupPath = path.join(packageDir, '.pack-package.json.backup');
9
+ const packageJsonPath = path.join(packageDir, 'package.json');
10
+
11
+ const packageJson = await readJson(packageJsonPath);
12
+ const resolvedVersions = {
13
+ '@t3x-dev/api': (await readJson(path.join(repoRoot, 'packages', 'api', 'package.json'))).version,
14
+ '@t3x-dev/cli': (await readJson(path.join(repoRoot, 'apps', 'cli', 'package.json'))).version,
15
+ '@t3x-dev/mcp': (await readJson(path.join(repoRoot, 'apps', 'mcp', 'package.json'))).version,
16
+ '@t3x-dev/storage': (await readJson(path.join(repoRoot, 'packages', 'storage', 'package.json')))
17
+ .version,
18
+ };
19
+
20
+ await writeJson(backupPath, packageJson);
21
+
22
+ const rewrittenPackageJson = {
23
+ ...packageJson,
24
+ dependencies: {
25
+ ...packageJson.dependencies,
26
+ ...resolvedVersions,
27
+ },
28
+ };
29
+
30
+ await writeJson(packageJsonPath, rewrittenPackageJson);
31
+ console.log('[prepack] Rewrote workspace dependencies in apps/local/package.json');
@@ -0,0 +1,78 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { spawnSync } from 'node:child_process';
4
+ import fs from 'node:fs/promises';
5
+ import os from 'node:os';
6
+ import path from 'node:path';
7
+ import {
8
+ assertRuntimeLayout,
9
+ ensureDir,
10
+ fileExists,
11
+ getLocalPackageDir,
12
+ getPlatformKey,
13
+ getRepoRoot,
14
+ getRuntimeArtifactFileName,
15
+ getRuntimeArtifactsDir,
16
+ readJson,
17
+ } from './runtime-helpers.mjs';
18
+
19
+ const packageDir = getLocalPackageDir();
20
+ const repoRoot = getRepoRoot();
21
+ const localPackageJson = await readJson(path.join(packageDir, 'package.json'));
22
+ const platformKey = process.env.T3X_LOCAL_RUNTIME_PLATFORM ?? getPlatformKey();
23
+ const artifactDir = path.resolve(
24
+ process.env.T3X_LOCAL_RUNTIME_OUTPUT_DIR ?? getRuntimeArtifactsDir(packageDir)
25
+ );
26
+ const artifactFileName = getRuntimeArtifactFileName(localPackageJson.version, platformKey);
27
+ const artifactPath = path.join(artifactDir, artifactFileName);
28
+ const stagingDir = await fs.mkdtemp(path.join(os.tmpdir(), 't3x-local-runtime-'));
29
+
30
+ const sources = {
31
+ apiDist: path.join(repoRoot, 'apps', 'api', 'dist'),
32
+ webStandalone: path.join(repoRoot, 'apps', 'web', '.next', 'standalone'),
33
+ webStatic: path.join(repoRoot, 'apps', 'web', '.next', 'static'),
34
+ webPublic: path.join(repoRoot, 'apps', 'web', 'public'),
35
+ };
36
+
37
+ const missingSources = [];
38
+ for (const [label, sourcePath] of Object.entries(sources)) {
39
+ if (!(await fileExists(sourcePath))) {
40
+ missingSources.push(`${label}: ${sourcePath}`);
41
+ }
42
+ }
43
+
44
+ if (missingSources.length > 0) {
45
+ throw new Error(
46
+ 'Missing runtime build outputs. Build API and Web before preparing the runtime tarball.\n' +
47
+ missingSources.join('\n')
48
+ );
49
+ }
50
+
51
+ try {
52
+ await ensureDir(path.join(stagingDir, 'api'));
53
+ await ensureDir(path.join(stagingDir, 'web'));
54
+ await ensureDir(artifactDir);
55
+
56
+ await fs.cp(sources.apiDist, path.join(stagingDir, 'api', 'dist'), { recursive: true });
57
+ await fs.cp(sources.webStandalone, path.join(stagingDir, 'web', 'standalone'), {
58
+ recursive: true,
59
+ verbatimSymlinks: true,
60
+ });
61
+ await fs.cp(sources.webStatic, path.join(stagingDir, 'web', 'static'), { recursive: true });
62
+ await fs.cp(sources.webPublic, path.join(stagingDir, 'web', 'public'), { recursive: true });
63
+
64
+ await assertRuntimeLayout(stagingDir);
65
+
66
+ const tarResult = spawnSync('tar', ['-czf', artifactPath, '-C', stagingDir, '.'], {
67
+ cwd: repoRoot,
68
+ stdio: 'inherit',
69
+ });
70
+
71
+ if (tarResult.status !== 0) {
72
+ throw new Error(`tar failed with exit code ${String(tarResult.status)}`);
73
+ }
74
+
75
+ console.log(`[prepare-runtime] Wrote ${artifactPath}`);
76
+ } finally {
77
+ await fs.rm(stagingDir, { recursive: true, force: true });
78
+ }
@@ -0,0 +1,115 @@
1
+ import crypto from 'node:crypto';
2
+ import fs from 'node:fs/promises';
3
+ import path from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+
6
+ export const SUPPORTED_RUNTIME_PATHS = [
7
+ path.join('api', 'dist', 'index.js'),
8
+ path.join('web', 'standalone', 'apps', 'web', 'server.js'),
9
+ path.join('web', 'static'),
10
+ path.join('web', 'public'),
11
+ ];
12
+
13
+ export function getLocalPackageDir() {
14
+ return path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
15
+ }
16
+
17
+ export function getRepoRoot() {
18
+ return path.resolve(getLocalPackageDir(), '../..');
19
+ }
20
+
21
+ export async function findWorkspaceRepoRoot(startDir) {
22
+ let current = path.resolve(startDir);
23
+
24
+ while (current !== path.dirname(current)) {
25
+ if (await fileExists(path.join(current, 'pnpm-workspace.yaml'))) {
26
+ return current;
27
+ }
28
+ current = path.dirname(current);
29
+ }
30
+
31
+ return null;
32
+ }
33
+
34
+ export function getPlatformKey() {
35
+ return `${process.platform}-${process.arch}`;
36
+ }
37
+
38
+ export function getRuntimeArtifactFileName(packageVersion, platformKey) {
39
+ return `t3x-local-runtime-${packageVersion}-${platformKey}.tar.gz`;
40
+ }
41
+
42
+ export function getRuntimeArtifactsDir(packageDir = getLocalPackageDir()) {
43
+ return path.join(packageDir, 'runtime-artifacts');
44
+ }
45
+
46
+ export function getRuntimeManifestPath(packageDir = getLocalPackageDir()) {
47
+ return path.join(packageDir, 'runtime-manifest.json');
48
+ }
49
+
50
+ export function getDefaultInstalledRuntimeDir(packageDir = getLocalPackageDir()) {
51
+ return path.join(packageDir, 'runtime', getPlatformKey());
52
+ }
53
+
54
+ export async function ensureDir(dirPath) {
55
+ await fs.mkdir(dirPath, { recursive: true });
56
+ }
57
+
58
+ export async function readJson(filePath) {
59
+ return JSON.parse(await fs.readFile(filePath, 'utf8'));
60
+ }
61
+
62
+ export async function writeJson(filePath, value) {
63
+ await fs.writeFile(filePath, `${JSON.stringify(value, null, 2)}\n`, 'utf8');
64
+ }
65
+
66
+ export async function sha256File(filePath) {
67
+ const hash = crypto.createHash('sha256');
68
+ const file = await fs.readFile(filePath);
69
+ hash.update(file);
70
+ return hash.digest('hex');
71
+ }
72
+
73
+ export async function statSize(filePath) {
74
+ const stat = await fs.stat(filePath);
75
+ return stat.size;
76
+ }
77
+
78
+ export async function assertRuntimeLayout(rootDir) {
79
+ const missing = [];
80
+
81
+ for (const relativePath of SUPPORTED_RUNTIME_PATHS) {
82
+ const absolutePath = path.join(rootDir, relativePath);
83
+ try {
84
+ await fs.access(absolutePath);
85
+ } catch {
86
+ missing.push(absolutePath);
87
+ }
88
+ }
89
+
90
+ if (missing.length > 0) {
91
+ throw new Error(`Runtime directory is missing required files:\n${missing.join('\n')}`);
92
+ }
93
+ }
94
+
95
+ export async function fileExists(filePath) {
96
+ try {
97
+ await fs.access(filePath);
98
+ return true;
99
+ } catch {
100
+ return false;
101
+ }
102
+ }
103
+
104
+ export function resolveMirrorArtifactLocation(mirror, fileName) {
105
+ if (
106
+ mirror.startsWith('http://') ||
107
+ mirror.startsWith('https://') ||
108
+ mirror.startsWith('file://')
109
+ ) {
110
+ const base = mirror.endsWith('/') ? mirror : `${mirror}/`;
111
+ return new URL(fileName, base).toString();
112
+ }
113
+
114
+ return path.resolve(mirror, fileName);
115
+ }