@coreframe/scripts 0.0.0 → 0.1.0

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.
Files changed (96) hide show
  1. package/bin/coreframe.js +35 -0
  2. package/config.test.ts +15 -0
  3. package/config.ts +9 -0
  4. package/coreframe.ts +272 -0
  5. package/db/client.ts +72 -0
  6. package/db/config.ts +41 -0
  7. package/db/drizzle-kit.test.ts +27 -0
  8. package/db/drizzle-kit.ts +39 -0
  9. package/db/ensure.ts +67 -0
  10. package/db/migrate-held.test.ts +81 -0
  11. package/db/migrate-held.ts +166 -0
  12. package/db/migrate.ts +39 -0
  13. package/db/project.ts +28 -0
  14. package/db/reset.ts +24 -0
  15. package/db/seed.ts +38 -0
  16. package/db/setup.ts +18 -0
  17. package/deploy/check-migrations.ts +31 -0
  18. package/deploy/checks.ts +38 -0
  19. package/deploy/config.test.ts +24 -0
  20. package/deploy/config.ts +68 -0
  21. package/deploy/migrations.test.ts +110 -0
  22. package/deploy/migrations.ts +317 -0
  23. package/deploy/pending-migrations.test.ts +93 -0
  24. package/deploy/pending-migrations.ts +45 -0
  25. package/deploy/tauri-release.test.ts +196 -0
  26. package/deploy/tauri-release.ts +816 -0
  27. package/deploy/version.test.ts +29 -0
  28. package/deploy/version.ts +65 -0
  29. package/deploy.test.ts +101 -0
  30. package/deploy.ts +346 -0
  31. package/dev.test.ts +153 -0
  32. package/dev.ts +155 -0
  33. package/dist/config.d.ts +5 -0
  34. package/dist/config.js +3 -0
  35. package/dist/coreframe.d.ts +280 -0
  36. package/dist/coreframe.js +240 -0
  37. package/dist/db/client.d.ts +22 -0
  38. package/dist/db/client.js +29 -0
  39. package/dist/db/config.d.ts +21 -0
  40. package/dist/db/config.js +32 -0
  41. package/dist/db/drizzle-kit.d.ts +8 -0
  42. package/dist/db/drizzle-kit.js +22 -0
  43. package/dist/db/ensure.d.ts +2 -0
  44. package/dist/db/ensure.js +40 -0
  45. package/dist/db/migrate-held.d.ts +12 -0
  46. package/dist/db/migrate-held.js +119 -0
  47. package/dist/db/migrate.d.ts +2 -0
  48. package/dist/db/migrate.js +25 -0
  49. package/dist/db/project.d.ts +8 -0
  50. package/dist/db/project.js +9 -0
  51. package/dist/db/reset.d.ts +2 -0
  52. package/dist/db/reset.js +19 -0
  53. package/dist/db/seed.d.ts +3 -0
  54. package/dist/db/seed.js +23 -0
  55. package/dist/db/setup.d.ts +3 -0
  56. package/dist/db/setup.js +15 -0
  57. package/dist/deploy/check-migrations.d.ts +2 -0
  58. package/dist/deploy/check-migrations.js +18 -0
  59. package/dist/deploy/checks.d.ts +2 -0
  60. package/dist/deploy/checks.js +27 -0
  61. package/dist/deploy/config.d.ts +50 -0
  62. package/dist/deploy/config.js +9 -0
  63. package/dist/deploy/migrations.d.ts +21 -0
  64. package/dist/deploy/migrations.js +203 -0
  65. package/dist/deploy/pending-migrations.d.ts +5 -0
  66. package/dist/deploy/pending-migrations.js +30 -0
  67. package/dist/deploy/tauri-release.d.ts +85 -0
  68. package/dist/deploy/tauri-release.js +512 -0
  69. package/dist/deploy/version.d.ts +18 -0
  70. package/dist/deploy/version.js +41 -0
  71. package/dist/deploy.d.ts +17 -0
  72. package/dist/deploy.js +247 -0
  73. package/dist/dev.d.ts +13 -0
  74. package/dist/dev.js +96 -0
  75. package/dist/image-generator.d.ts +2 -0
  76. package/dist/image-generator.js +59 -0
  77. package/dist/project.d.ts +1 -0
  78. package/dist/project.js +13 -0
  79. package/dist/typecheck.d.ts +2 -0
  80. package/dist/typecheck.js +45 -0
  81. package/dist/upgrade/check-skill-current.d.ts +6 -0
  82. package/dist/upgrade/check-skill-current.js +88 -0
  83. package/dist/upgrade/ensure-template-remote.d.ts +6 -0
  84. package/dist/upgrade/ensure-template-remote.js +57 -0
  85. package/dist/upgrade/fast-forward-template-files.d.ts +7 -0
  86. package/dist/upgrade/fast-forward-template-files.js +280 -0
  87. package/image-generator.ts +84 -0
  88. package/package.json +48 -8
  89. package/project.ts +6 -0
  90. package/tsconfig.build.json +12 -0
  91. package/tsconfig.json +20 -0
  92. package/typecheck.ts +55 -0
  93. package/upgrade/check-skill-current.ts +112 -0
  94. package/upgrade/ensure-template-remote.ts +79 -0
  95. package/upgrade/fast-forward-template-files.ts +408 -0
  96. package/readme.md +0 -1
package/dist/deploy.js ADDED
@@ -0,0 +1,247 @@
1
+ #!/usr/bin/env node
2
+ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
3
+ if (typeof path === "string" && /^\.\.?\//.test(path)) {
4
+ return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
5
+ return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
6
+ });
7
+ }
8
+ return path;
9
+ };
10
+ import { access, writeFile } from "node:fs/promises";
11
+ import { resolve } from "node:path";
12
+ import { pathToFileURL } from "node:url";
13
+ import * as clack from "@clack/prompts";
14
+ import nanoSpawn from "nano-spawn";
15
+ import { createProjectDeployDatabase } from "./db/project.js";
16
+ import { runPostDeployChecks } from "./deploy/checks.js";
17
+ import { resolveDeployConfig } from "./deploy/config.js";
18
+ import { collectDeployMigrationFiles, formatMigrationList, formatMigrationScanFailures, scanDeployMigrationFiles, } from "./deploy/migrations.js";
19
+ import { filterPendingMigrations, readAppliedMigrationNames } from "./deploy/pending-migrations.js";
20
+ import { parseTauriReleasePlatform, prepareTauriRelease, resolveTauriReleasePlan, restoreTauriReleaseMetadata, uploadTauriReleaseToB2, validateTauriReleasePlan, writeTauriReleaseMetadata, } from "./deploy/tauri-release.js";
21
+ import { nextReleaseVersion, readPackageJson, releaseName, writePackageVersion, } from "./deploy/version.js";
22
+ const packagePath = "package.json";
23
+ const projectConfigPath = "coreframe.config.ts";
24
+ export async function deployProject(argv = process.argv.slice(2), env = process.env) {
25
+ const args = parseDeployArgs(argv);
26
+ const { packageJson, source: originalPackageSource } = await readPackageJson(packagePath);
27
+ const version = nextReleaseVersion(packageJson.version);
28
+ const name = releaseName(packageJson.name, version);
29
+ const environment = env.DEPLOY_ENV ?? "production";
30
+ const releaseEnv = createReleaseEnv(env, name, packageJson.name, version);
31
+ const projectConfig = await readProjectConfig();
32
+ const config = resolveDeployConfig(projectConfig.deploy ?? {}, env);
33
+ const tauriReleasePlan = resolveTauriReleasePlan({
34
+ args,
35
+ config: config.tauriRelease,
36
+ env,
37
+ });
38
+ let externalMutated = false;
39
+ let tauriMetadataSnapshot = { files: [] };
40
+ clack.intro(`Deploy ${name}`);
41
+ validateDeployArgs(args);
42
+ requireEnv(env, ["POSTHOG_API_KEY", "POSTHOG_PROJECT_ID"]);
43
+ await ensureCleanGit();
44
+ await validateTauriReleasePlan(tauriReleasePlan);
45
+ const database = await createProjectDeployDatabase({ env });
46
+ const pendingMigrations = await collectPendingMigrationsWithClose(database);
47
+ const migrationFailures = await scanDeployMigrationFiles(pendingMigrations);
48
+ if (migrationFailures.length > 0) {
49
+ throw new Error(`Pending deploy migrations contain unsafe SQL. Move unsafe statements to held migrations.\n${formatMigrationScanFailures(migrationFailures)}`);
50
+ }
51
+ const shouldRunMigrations = await chooseMigrationExecution(args, pendingMigrations);
52
+ try {
53
+ await writePackageVersion(packagePath, packageJson, version);
54
+ tauriMetadataSnapshot = await writeTauriReleaseMetadata(tauriReleasePlan, version);
55
+ clack.log.step("Running pre-deploy build");
56
+ await nanoSpawn("pnpm", ["build"], {
57
+ env: createBuildCheckEnv(releaseEnv),
58
+ stdio: "inherit",
59
+ });
60
+ const tauriRelease = tauriReleasePlan.enabled
61
+ ? await prepareTauriRelease({
62
+ env: createBuildCheckEnv(releaseEnv),
63
+ plan: tauriReleasePlan,
64
+ releaseName: name,
65
+ version,
66
+ })
67
+ : undefined;
68
+ clack.log.step("Running release build and uploading PostHog source maps");
69
+ externalMutated = true;
70
+ await nanoSpawn("pnpm", ["build"], {
71
+ env: releaseEnv,
72
+ stdio: "inherit",
73
+ });
74
+ if (shouldRunMigrations) {
75
+ clack.log.step("Running deploy-safe database migrations");
76
+ await nanoSpawn("pnpm", ["db", "migrate"], {
77
+ env: releaseEnv,
78
+ stdio: "inherit",
79
+ });
80
+ }
81
+ clack.log.step("Deploying Worker and static assets to Cloudflare");
82
+ await nanoSpawn("pnpm", [
83
+ "exec",
84
+ "wrangler",
85
+ "deploy",
86
+ "--strict",
87
+ "--keep-vars",
88
+ "--tag",
89
+ name,
90
+ "--message",
91
+ `Deploy ${name}`,
92
+ "--var",
93
+ `POSTHOG_RELEASE:${name}`,
94
+ ], {
95
+ env: releaseEnv,
96
+ stdio: "inherit",
97
+ });
98
+ if (!args.skipPostDeployChecks) {
99
+ clack.log.step("Running post-deploy checks");
100
+ await runPostDeployChecks(config, {
101
+ environment,
102
+ productionUrl: config.productionUrl,
103
+ releaseName: name,
104
+ version,
105
+ });
106
+ }
107
+ if (tauriRelease) {
108
+ clack.log.step("Uploading Tauri release artifacts to Backblaze B2");
109
+ await uploadTauriReleaseToB2(tauriReleasePlan, tauriRelease);
110
+ }
111
+ clack.outro(`Deployed ${name}`);
112
+ }
113
+ catch (error) {
114
+ if (!externalMutated) {
115
+ await writeFile(packagePath, originalPackageSource);
116
+ await restoreTauriReleaseMetadata(tauriMetadataSnapshot);
117
+ }
118
+ throw error;
119
+ }
120
+ }
121
+ export async function readProjectConfig() {
122
+ const configPath = resolve(projectConfigPath);
123
+ try {
124
+ await access(configPath);
125
+ }
126
+ catch {
127
+ return {};
128
+ }
129
+ const configUrl = pathToFileURL(configPath).href;
130
+ const module = (await import(__rewriteRelativeImportExtension(configUrl)));
131
+ return module.default ?? {};
132
+ }
133
+ export function parseDeployArgs(argv) {
134
+ return {
135
+ runMigrations: argv.includes("--run-migrations") || argv.includes("--yes"),
136
+ skipMigrations: argv.includes("--skip-migrations"),
137
+ skipPostDeployChecks: argv.includes("--skip-post-deploy-checks"),
138
+ tauriLocalPlatforms: parseRepeatedOption(argv, "--tauri-local-platform").map((platform) => parseTauriReleasePlatform(platform)),
139
+ tauriWorkflowName: parseOptionalOption(argv, "--tauri-workflow-name"),
140
+ tauriWorkflowPlatforms: parseRepeatedOption(argv, "--tauri-workflow-platform").map((platform) => parseTauriReleasePlatform(platform)),
141
+ tauriWorkflowRef: parseOptionalOption(argv, "--tauri-workflow-ref"),
142
+ yes: argv.includes("--yes"),
143
+ };
144
+ }
145
+ function parseRepeatedOption(argv, name) {
146
+ const values = [];
147
+ for (let index = 0; index < argv.length; index += 1) {
148
+ const arg = argv[index];
149
+ if (arg === name) {
150
+ const value = argv[index + 1];
151
+ if (!value || value.startsWith("--")) {
152
+ throw new Error(`${name} requires a value.`);
153
+ }
154
+ values.push(...value.split(",").filter(Boolean));
155
+ index += 1;
156
+ continue;
157
+ }
158
+ if (arg.startsWith(`${name}=`)) {
159
+ values.push(...arg
160
+ .slice(name.length + 1)
161
+ .split(",")
162
+ .filter(Boolean));
163
+ }
164
+ }
165
+ return values;
166
+ }
167
+ function parseOptionalOption(argv, name) {
168
+ const values = parseRepeatedOption(argv, name);
169
+ if (values.length > 1) {
170
+ throw new Error(`${name} can only be provided once.`);
171
+ }
172
+ return values[0];
173
+ }
174
+ async function collectPendingMigrationsWithClose(database) {
175
+ try {
176
+ return await collectPendingMigrations(database);
177
+ }
178
+ finally {
179
+ await database.close?.();
180
+ }
181
+ }
182
+ async function collectPendingMigrations(database) {
183
+ const migrations = await collectDeployMigrationFiles();
184
+ const appliedMigrations = await readAppliedMigrationNames(database);
185
+ return filterPendingMigrations(migrations, appliedMigrations);
186
+ }
187
+ async function chooseMigrationExecution(args, pendingMigrations) {
188
+ if (pendingMigrations.length === 0) {
189
+ clack.log.info("No pending deploy migrations.");
190
+ return false;
191
+ }
192
+ clack.note(formatMigrationList(pendingMigrations), "Pending deploy migrations");
193
+ if (args.runMigrations) {
194
+ return true;
195
+ }
196
+ if (args.skipMigrations) {
197
+ return false;
198
+ }
199
+ if (!process.stdout.isTTY) {
200
+ throw new Error("Pending migrations require --run-migrations or --skip-migrations in noninteractive deploys.");
201
+ }
202
+ const answer = await clack.confirm({
203
+ initialValue: true,
204
+ message: "Run these deploy-safe database migrations before Cloudflare deploy?",
205
+ });
206
+ if (clack.isCancel(answer)) {
207
+ clack.cancel("Deploy cancelled.");
208
+ throw new Error("Deploy cancelled.");
209
+ }
210
+ return answer;
211
+ }
212
+ async function ensureCleanGit() {
213
+ const { stdout: status } = await nanoSpawn("git", ["status", "--porcelain"], { stdin: "ignore" });
214
+ if (status.trim().length > 0) {
215
+ throw new Error("Deploy requires a clean git tree before the release version is applied.");
216
+ }
217
+ }
218
+ function validateDeployArgs(args) {
219
+ if (args.runMigrations && args.skipMigrations) {
220
+ throw new Error("Use only one of --run-migrations, --skip-migrations, or --yes.");
221
+ }
222
+ }
223
+ function requireEnv(env, names) {
224
+ const missing = names.filter((name) => !env[name]);
225
+ if (missing.length > 0) {
226
+ throw new Error(`Missing required deploy environment variables: ${missing.join(", ")}`);
227
+ }
228
+ return Object.fromEntries(names.map((name) => [name, env[name]]));
229
+ }
230
+ function createReleaseEnv(env, name, packageName, version) {
231
+ return {
232
+ ...env,
233
+ POSTHOG_RELEASE: name,
234
+ POSTHOG_RELEASE_NAME: env.POSTHOG_RELEASE_NAME ?? packageName,
235
+ POSTHOG_RELEASE_VERSION: env.POSTHOG_RELEASE_VERSION ?? version,
236
+ };
237
+ }
238
+ function createBuildCheckEnv(env) {
239
+ const { POSTHOG_API_KEY: _apiKey, POSTHOG_PROJECT_ID: _projectId, ...buildEnv } = env;
240
+ return buildEnv;
241
+ }
242
+ if (import.meta.main) {
243
+ deployProject().catch((error) => {
244
+ console.error(error);
245
+ process.exit(1);
246
+ });
247
+ }
package/dist/dev.d.ts ADDED
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env node
2
+ import { spawn as nodeSpawn } from "node:child_process";
3
+ type Spawn = typeof nodeSpawn;
4
+ export type DevServerOptions = {
5
+ cwd?: string;
6
+ env?: NodeJS.ProcessEnv;
7
+ logPath?: string;
8
+ platform?: DevPlatform;
9
+ spawn?: Spawn;
10
+ };
11
+ export type DevPlatform = "android" | "desktop" | "ios" | "web";
12
+ export declare function runDevServer({ cwd, env, logPath, platform, spawn, }?: DevServerOptions): Promise<void>;
13
+ export {};
package/dist/dev.js ADDED
@@ -0,0 +1,96 @@
1
+ #!/usr/bin/env node
2
+ import { spawn as nodeSpawn } from "node:child_process";
3
+ import { createWriteStream } from "node:fs";
4
+ import { mkdir } from "node:fs/promises";
5
+ import path from "node:path";
6
+ import process from "node:process";
7
+ const defaultLogPath = ".agents/logs/dev-server.log";
8
+ const nodeModulesBinPath = path.resolve("node_modules", ".bin");
9
+ const pnpmCommand = process.platform === "win32" ? "pnpm.cmd" : "pnpm";
10
+ const reactRouterCommand = process.platform === "win32" ? "react-router.cmd" : "react-router";
11
+ export async function runDevServer({ cwd = process.cwd(), env = process.env, logPath = defaultLogPath, platform = "web", spawn = nodeSpawn, } = {}) {
12
+ const pathEnvValue = env.PATH ?? env.Path ?? "";
13
+ const shouldTee = !("TAURI_ENV_PLATFORM" in env);
14
+ const log = shouldTee ? await createDevLog(logPath) : undefined;
15
+ try {
16
+ if (platform === "web") {
17
+ await runDevProcess(spawn, pnpmCommand, ["db", "ensure"], {
18
+ cwd,
19
+ env,
20
+ log,
21
+ });
22
+ await runDevProcess(spawn, reactRouterCommand, ["dev"], {
23
+ cwd,
24
+ env: {
25
+ ...env,
26
+ CLOUDFLARE_ENV: "development",
27
+ PATH: nodeModulesBinPath + path.delimiter + pathEnvValue,
28
+ },
29
+ log,
30
+ });
31
+ return;
32
+ }
33
+ await runDevProcess(spawn, pnpmCommand, tauriDevArgs(platform), {
34
+ cwd,
35
+ env,
36
+ log,
37
+ });
38
+ }
39
+ finally {
40
+ await closeLog(log);
41
+ }
42
+ }
43
+ async function createDevLog(logPath) {
44
+ await mkdir(path.dirname(logPath), { recursive: true });
45
+ return createWriteStream(logPath, { flags: "w" });
46
+ }
47
+ function closeLog(log) {
48
+ if (!log) {
49
+ return Promise.resolve();
50
+ }
51
+ return new Promise((resolve, reject) => {
52
+ log.once("error", reject);
53
+ log.end(resolve);
54
+ });
55
+ }
56
+ function tauriDevArgs(platform) {
57
+ return ["--dir=tauri", "tauri", ...(platform === "desktop" ? [] : [platform]), "dev"];
58
+ }
59
+ function runDevProcess(spawn, command, args, { cwd, env, log, }) {
60
+ return new Promise((resolve, reject) => {
61
+ const child = spawn(command, args, {
62
+ cwd,
63
+ env,
64
+ stdio: log ? ["inherit", "pipe", "pipe"] : "inherit",
65
+ });
66
+ if (log) {
67
+ if (!child.stdout || !child.stderr) {
68
+ reject(new Error(`${[command, ...args].join(" ")} did not provide output streams`));
69
+ return;
70
+ }
71
+ child.stdout.on("data", (chunk) => {
72
+ process.stdout.write(chunk);
73
+ log.write(chunk);
74
+ });
75
+ child.stderr.on("data", (chunk) => {
76
+ process.stderr.write(chunk);
77
+ log.write(chunk);
78
+ });
79
+ }
80
+ child.on("error", reject);
81
+ child.on("close", (code, signal) => {
82
+ if (code === 0) {
83
+ resolve();
84
+ return;
85
+ }
86
+ const commandText = [command, ...args].join(" ");
87
+ reject(new Error(`${commandText} failed${signal ? ` with signal ${signal}` : ` with exit code ${code ?? 1}`}`));
88
+ });
89
+ });
90
+ }
91
+ if (import.meta.main) {
92
+ runDevServer().catch((error) => {
93
+ console.error(error);
94
+ process.exit(1);
95
+ });
96
+ }
@@ -0,0 +1,2 @@
1
+ import type { Plugin } from "vite";
2
+ export declare function imageGenerator(): Plugin;
@@ -0,0 +1,59 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { glob } from "tinyglobby";
4
+ export function imageGenerator() {
5
+ return {
6
+ name: "image-generator",
7
+ async buildStart() {
8
+ const files = await glob(["src/**/*@3x.{png,webp}", "public/**/*@3x.{png,webp}"]);
9
+ console.log(`[image-generator] Found ${files.length} @3x images.`);
10
+ await Promise.all(files.map((file) => generateDensityImages(file)));
11
+ },
12
+ };
13
+ }
14
+ async function generateDensityImages(file) {
15
+ const dir = path.dirname(file);
16
+ const ext = path.extname(file);
17
+ const base = path.basename(file, `@3x${ext}`);
18
+ const outputImages = [
19
+ {
20
+ outputPath: path.join(dir, `${base}@2x${ext}`),
21
+ scale: 2 / 3,
22
+ },
23
+ {
24
+ outputPath: path.join(dir, `${base}@1x${ext}`),
25
+ scale: 1 / 3,
26
+ },
27
+ ].filter((image) => !fs.existsSync(image.outputPath));
28
+ if (outputImages.length === 0) {
29
+ return;
30
+ }
31
+ const { default: sharp } = await import("sharp");
32
+ let width;
33
+ try {
34
+ width = (await sharp(file).metadata()).width;
35
+ }
36
+ catch (error) {
37
+ console.error(`[image-generator] Failed to read metadata for ${file}`, error);
38
+ return;
39
+ }
40
+ if (!width) {
41
+ console.error(`[image-generator] Skipping ${file}; image width could not be read.`);
42
+ return;
43
+ }
44
+ await Promise.all(outputImages.map((image) => generateDensityImage({
45
+ file,
46
+ outputPath: image.outputPath,
47
+ sharp,
48
+ width: Math.round(width * image.scale),
49
+ })));
50
+ }
51
+ async function generateDensityImage(options) {
52
+ try {
53
+ console.log(`[image-generator] Generating ${options.outputPath} (${options.width}w)`);
54
+ await options.sharp(options.file).resize({ width: options.width }).toFile(options.outputPath);
55
+ }
56
+ catch (error) {
57
+ console.error(`[image-generator] Failed to process ${options.file} for ${options.outputPath}`, error);
58
+ }
59
+ }
@@ -0,0 +1 @@
1
+ export declare function importProjectModule<T>(path: string): Promise<T>;
@@ -0,0 +1,13 @@
1
+ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
2
+ if (typeof path === "string" && /^\.\.?\//.test(path)) {
3
+ return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
4
+ return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
5
+ });
6
+ }
7
+ return path;
8
+ };
9
+ import { resolve } from "node:path";
10
+ import { pathToFileURL } from "node:url";
11
+ export async function importProjectModule(path) {
12
+ return (await import(__rewriteRelativeImportExtension(pathToFileURL(resolve(process.cwd(), path)).href)));
13
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export declare function typecheckProject(): Promise<void>;
@@ -0,0 +1,45 @@
1
+ #!/usr/bin/env node
2
+ import { access } from "node:fs/promises";
3
+ import path from "node:path";
4
+ import process from "node:process";
5
+ import { supervise } from "procband";
6
+ const nodeModulesBinPath = path.resolve("node_modules", ".bin");
7
+ process.env.PATH = nodeModulesBinPath + path.delimiter + process.env.PATH;
8
+ const tscCommand = process.platform === "win32" ? "tsc.cmd" : "tsc";
9
+ const projects = [
10
+ { name: "scripts", path: "scripts/tsconfig.json" },
11
+ { name: "shared", path: "src/shared/tsconfig.json" },
12
+ { name: "api", path: "src/api/tsconfig.json" },
13
+ { name: "worker", path: "src/api/worker/tsconfig.json" },
14
+ { name: "web", path: "src/web/tsconfig.json" },
15
+ ];
16
+ export async function typecheckProject() {
17
+ const existingProjects = [];
18
+ for (const project of projects) {
19
+ try {
20
+ await access(project.path);
21
+ existingProjects.push(project);
22
+ }
23
+ catch (error) {
24
+ if (!(error instanceof Error && "code" in error && error.code === "ENOENT")) {
25
+ throw error;
26
+ }
27
+ }
28
+ }
29
+ const checks = existingProjects.map((project) => supervise({
30
+ name: project.name,
31
+ command: tscCommand,
32
+ args: ["-p", project.path],
33
+ }));
34
+ const results = await Promise.all(checks.map((check) => check.wait()));
35
+ const failed = results.filter((result) => result.exitCode !== 0);
36
+ if (failed.length > 0) {
37
+ process.exitCode = failed[0].exitCode;
38
+ }
39
+ }
40
+ if (import.meta.main) {
41
+ typecheckProject().catch((error) => {
42
+ console.error(error);
43
+ process.exit(1);
44
+ });
45
+ }
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env node
2
+ export type CheckUpgradeSkillOptions = {
3
+ apply: boolean;
4
+ target: string;
5
+ };
6
+ export declare function checkUpgradeSkillCurrent({ apply, target }: CheckUpgradeSkillOptions): 0 | 20;
@@ -0,0 +1,88 @@
1
+ #!/usr/bin/env node
2
+ import { spawnSync } from "node:child_process";
3
+ import { existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
4
+ import { tmpdir } from "node:os";
5
+ import { join } from "node:path";
6
+ const skillPath = ".agents/skills/coreframe-upgrade/SKILL.md";
7
+ function git(args, options = {}) {
8
+ return spawnSync("git", args, options);
9
+ }
10
+ function errorDetail(result) {
11
+ return result.stderr.toString("utf8").trim() || result.stdout.toString("utf8").trim();
12
+ }
13
+ function readGitFile(ref, path) {
14
+ const result = git(["show", `${ref}:${path}`], { encoding: "utf8" });
15
+ if (result.status !== 0) {
16
+ const detail = errorDetail(result);
17
+ throw new Error(`Could not read ${path} at ${ref}.${detail ? ` ${detail}` : ""}`);
18
+ }
19
+ return result.stdout.toString();
20
+ }
21
+ function getRepoRoot() {
22
+ const result = git(["rev-parse", "--show-toplevel"], { encoding: "utf8" });
23
+ if (result.status !== 0) {
24
+ const detail = errorDetail(result);
25
+ throw new Error(`Could not find git repository root.${detail ? ` ${detail}` : ""}`);
26
+ }
27
+ return result.stdout.toString().trim();
28
+ }
29
+ function printDiff(localPath, targetText) {
30
+ const dir = mkdtempSync(join(tmpdir(), "coreframe-upgrade-skill-"));
31
+ const targetPath = join(dir, "SKILL.md");
32
+ try {
33
+ writeFileSync(targetPath, targetText);
34
+ const diff = git([
35
+ "--no-pager",
36
+ "diff",
37
+ "--no-index",
38
+ "--color=never",
39
+ "--",
40
+ localPath,
41
+ targetPath,
42
+ ]);
43
+ const output = errorDetail(diff);
44
+ if (output) {
45
+ console.log(output);
46
+ }
47
+ }
48
+ finally {
49
+ rmSync(dir, { force: true, recursive: true });
50
+ }
51
+ }
52
+ export function checkUpgradeSkillCurrent({ apply, target }) {
53
+ const repoRoot = getRepoRoot();
54
+ const localPath = join(repoRoot, skillPath);
55
+ if (!existsSync(localPath)) {
56
+ throw new Error(`Local ${skillPath} does not exist.`);
57
+ }
58
+ const localText = readFileSync(localPath, "utf8");
59
+ const targetText = readGitFile(target, skillPath);
60
+ if (localText === targetText) {
61
+ console.log(`Coreframe upgrade skill matches ${target}:${skillPath}.`);
62
+ return 0;
63
+ }
64
+ if (apply) {
65
+ writeFileSync(localPath, targetText);
66
+ console.error(`Updated ${skillPath} from ${target}. Reread it before continuing the upgrade.`);
67
+ return 20;
68
+ }
69
+ console.error(`Coreframe upgrade skill differs from ${target}:${skillPath}.`);
70
+ console.error(`Run again with --apply to update it, then reread ${skillPath} before continuing.`);
71
+ printDiff(localPath, targetText);
72
+ return 20;
73
+ }
74
+ if (import.meta.main) {
75
+ const targetIndex = process.argv.indexOf("--target");
76
+ const target = targetIndex === -1 ? null : process.argv[targetIndex + 1];
77
+ if (!target) {
78
+ console.error("Usage: coreframe upgrade check-skill-current --target <git-ref> [--apply]");
79
+ process.exit(64);
80
+ }
81
+ try {
82
+ process.exit(checkUpgradeSkillCurrent({ apply: process.argv.includes("--apply"), target }));
83
+ }
84
+ catch (error) {
85
+ console.error(error instanceof Error ? error.message : String(error));
86
+ process.exit(65);
87
+ }
88
+ }
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env node
2
+ export type EnsureTemplateRemoteOptions = {
3
+ fetch: boolean;
4
+ remote: string;
5
+ };
6
+ export declare function ensureTemplateRemote({ fetch, remote, }?: Partial<EnsureTemplateRemoteOptions>): void;
@@ -0,0 +1,57 @@
1
+ #!/usr/bin/env node
2
+ import { spawnSync } from "node:child_process";
3
+ const defaultRemote = "template";
4
+ const defaultUrl = "https://github.com/airlock-labs/coreframe.git";
5
+ function git(args) {
6
+ return spawnSync("git", args, { encoding: "utf8" });
7
+ }
8
+ function runGit(args) {
9
+ const result = git(args);
10
+ if (result.status !== 0) {
11
+ const detail = result.stderr.trim() || result.stdout.trim();
12
+ throw new Error(`Git command failed: git ${args.join(" ")}${detail ? `\n${detail}` : ""}`);
13
+ }
14
+ return result.stdout;
15
+ }
16
+ function getRemoteUrl(remote) {
17
+ const result = git(["remote", "get-url", remote]);
18
+ if (result.status === 2) {
19
+ return null;
20
+ }
21
+ if (result.status !== 0) {
22
+ const detail = result.stderr.trim() || result.stdout.trim();
23
+ throw new Error(`Could not inspect remote ${remote}.${detail ? `\n${detail}` : ""}`);
24
+ }
25
+ return result.stdout.trim();
26
+ }
27
+ export function ensureTemplateRemote({ fetch = true, remote = defaultRemote, } = {}) {
28
+ if (remote.length === 0) {
29
+ throw new Error("Remote name must not be empty.");
30
+ }
31
+ runGit(["rev-parse", "--show-toplevel"]);
32
+ const existingUrl = getRemoteUrl(remote);
33
+ if (existingUrl === null) {
34
+ runGit(["remote", "add", remote, defaultUrl]);
35
+ console.log(`Added ${remote} remote: ${defaultUrl}`);
36
+ }
37
+ else if (existingUrl === defaultUrl) {
38
+ console.log(`${remote} remote already points at ${defaultUrl}`);
39
+ }
40
+ else {
41
+ throw new Error(`${remote} remote points at ${existingUrl}, not ${defaultUrl}.\n` +
42
+ "Choose the intended template source before continuing, then update or rename the remote manually.");
43
+ }
44
+ if (fetch) {
45
+ runGit(["fetch", remote, "--tags", "--prune"]);
46
+ console.log(`Fetched ${remote}.`);
47
+ }
48
+ }
49
+ if (import.meta.main) {
50
+ try {
51
+ ensureTemplateRemote({ fetch: !process.argv.includes("--no-fetch") });
52
+ }
53
+ catch (error) {
54
+ console.error(error instanceof Error ? error.message : String(error));
55
+ process.exit(65);
56
+ }
57
+ }
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env node
2
+ export type FastForwardTemplateFilesOptions = {
3
+ apply: boolean;
4
+ base?: string;
5
+ target: string;
6
+ };
7
+ export declare function fastForwardTemplateFiles(args: FastForwardTemplateFilesOptions): void;