@stacksjs/actions 0.70.162 → 0.70.164
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/bump.js +5 -0
- package/dist/deploy/index.js +1 -1
- package/dist/dev/dashboard.js +2 -2
- package/dist/generate/index.d.ts +8 -0
- package/dist/generate/index.js +4 -1
- package/dist/helpers/utils.js +7 -4
- package/dist/upgrade/catalog.d.ts +60 -0
- package/dist/upgrade/catalog.js +113 -0
- package/dist/upgrade/framework-hooks.d.ts +36 -3
- package/dist/upgrade/framework-hooks.js +13 -1
- package/dist/upgrade/framework-utils.d.ts +24 -6
- package/dist/upgrade/framework-utils.js +35 -3
- package/dist/upgrade/framework.js +57 -8
- package/package.json +19 -19
package/README.md
CHANGED
|
@@ -49,7 +49,7 @@ For help, discussion about best practices, or any other conversation that would
|
|
|
49
49
|
|
|
50
50
|
For casual chit-chat with others using this package:
|
|
51
51
|
|
|
52
|
-
[Join the Stacks Discord Server](https://
|
|
52
|
+
[Join the Stacks Discord Server](https://stacksjs.com/discord)
|
|
53
53
|
|
|
54
54
|
## 📄 License
|
|
55
55
|
|
package/dist/bump.js
CHANGED
|
@@ -138,6 +138,11 @@ if (!isDryRun && existsSync(p.projectPath("bun.lock"))) {
|
|
|
138
138
|
writeFileSync(lockPath, previousLock);
|
|
139
139
|
throw error;
|
|
140
140
|
}
|
|
141
|
+
const expectedLockfileVersion = 1, versionMatch = readFileSync(lockPath, "utf8").match(/"lockfileVersion"\s*:\s*(\d+)/), producedVersion = versionMatch ? Number(versionMatch[1]) : null;
|
|
142
|
+
if (producedVersion !== expectedLockfileVersion) {
|
|
143
|
+
writeFileSync(lockPath, previousLock);
|
|
144
|
+
throw Error(`Release aborted: regenerating bun.lock produced lockfileVersion ${producedVersion ?? "unknown"}, but CI's Bun (1.3.x) requires v${expectedLockfileVersion}. You are releasing with a newer Bun (1.4.x writes v2). Re-run the release with Bun 1.3.x (e.g. via \`bunx bun@1.3.14\`) so CI can parse the lockfile.`);
|
|
145
|
+
}
|
|
141
146
|
}
|
|
142
147
|
function pinMetaCoreDeps(version) {
|
|
143
148
|
const metaPath = p.frameworkPath("core/package.json"), meta = JSON.parse(readFileSync(metaPath, "utf-8"));
|
package/dist/deploy/index.js
CHANGED
|
@@ -1014,7 +1014,7 @@ SVCEOF`,
|
|
|
1014
1014
|
} else
|
|
1015
1015
|
smtpSpinner.fail(`No Pantry-provided ${MAIL_TARGET_PLATFORM} mail binary found. Release ${MAIL_PACKAGE_DOMAIN}, then run the Pantry binary sync for that package.`);
|
|
1016
1016
|
} else {
|
|
1017
|
-
const smtpServerSrc = resolvePath(p.projectPath("storage/framework/core/cloud/src/imap/smtp-server.ts")), bundleOutDir =
|
|
1017
|
+
const smtpServerSrc = resolvePath(p.projectPath("storage/framework/core/cloud/src/imap/smtp-server.ts")), bundleOutDir = p.frameworkRuntimePath("tmp/smtp-bundle");
|
|
1018
1018
|
execSync(`bun build ${smtpServerSrc} --target=bun --outdir=${bundleOutDir}`, { stdio: "pipe" });
|
|
1019
1019
|
const bundlePath = joinPath(bundleOutDir, "smtp-server.js");
|
|
1020
1020
|
if (smtpFileExists(bundlePath)) {
|
package/dist/dev/dashboard.js
CHANGED
|
@@ -323,7 +323,7 @@ if (!proxyStarted)
|
|
|
323
323
|
console.warn("[Dashboard] Reverse proxy failed:", err);
|
|
324
324
|
return !1;
|
|
325
325
|
});
|
|
326
|
-
const dashboardHttpsUrl = dashboardDomain ? `https://${dashboardDomain}` : null, dashboardLocalUrl = `http://localhost:${dashboardPort}`, initialUrl = dashboardHttpsUrl && proxyStarted ? `${dashboardHttpsUrl}/` : `${dashboardLocalUrl}/`, elapsedMs = (Bun.nanoseconds() - startTime) / 1e6;
|
|
326
|
+
const dashboardHttpsUrl = dashboardDomain ? `https://${dashboardDomain}` : null, dashboardLocalUrl = `http://localhost:${dashboardPort}`, initialUrl = dashboardHttpsUrl && proxyStarted ? `${dashboardHttpsUrl}/` : `${dashboardLocalUrl}/`, nativeWindowUrl = `${dashboardLocalUrl}/`, elapsedMs = (Bun.nanoseconds() - startTime) / 1e6;
|
|
327
327
|
console.log();
|
|
328
328
|
console.log(` ${bold(cyan("stacks dashboard"))}`);
|
|
329
329
|
console.log();
|
|
@@ -396,7 +396,7 @@ if (createApp) {
|
|
|
396
396
|
await new Promise(() => {});
|
|
397
397
|
}
|
|
398
398
|
const app = createApp({
|
|
399
|
-
url:
|
|
399
|
+
url: nativeWindowUrl,
|
|
400
400
|
quiet: !verbose,
|
|
401
401
|
...craftBinaryPath && { craftPath: craftBinaryPath },
|
|
402
402
|
window: {
|
package/dist/generate/index.d.ts
CHANGED
|
@@ -32,5 +32,13 @@ export declare function generateTypes(options?: GeneratorOptions): Promise<void>
|
|
|
32
32
|
export declare function watchTypes(options?: GeneratorOptions): Promise<void>;
|
|
33
33
|
export declare function generatePantryConfig(): void;
|
|
34
34
|
export declare function generateSeeder(): Promise<void>;
|
|
35
|
+
/**
|
|
36
|
+
* Drops a `.framework` symlink in the project root pointing at
|
|
37
|
+
* `storage/framework`, so core developers can `cd .framework` instead of typing
|
|
38
|
+
* the full path. Purely a convenience; nothing in the framework depends on it.
|
|
39
|
+
*
|
|
40
|
+
* It used to be called `.stacks`, which now collides with the pre-relocation
|
|
41
|
+
* name of the runtime scratch directory (see `ensureRuntimeDirectories`).
|
|
42
|
+
*/
|
|
35
43
|
export declare function generateCoreSymlink(): Promise<void>;
|
|
36
44
|
export declare function generateOpenApiSpec(): Promise<void>;
|
package/dist/generate/index.js
CHANGED
|
@@ -149,7 +149,10 @@ export function generatePantryConfig() {
|
|
|
149
149
|
}
|
|
150
150
|
export async function generateSeeder() {}
|
|
151
151
|
export async function generateCoreSymlink() {
|
|
152
|
-
|
|
152
|
+
const link = projectPath(".framework");
|
|
153
|
+
if (fs.existsSync(link))
|
|
154
|
+
await runCommand(`rm -f ${link}`);
|
|
155
|
+
await runCommand(`ln -s ${frameworkPath()} ${link}`);
|
|
153
156
|
}
|
|
154
157
|
export async function generateOpenApiSpec() {
|
|
155
158
|
const { generateOpenApi } = await import("@stacksjs/api");
|
package/dist/helpers/utils.js
CHANGED
|
@@ -29,8 +29,10 @@ export function publishedActionCandidates(action, packageRoot) {
|
|
|
29
29
|
export function developmentConditionForProject(projectRoot) {
|
|
30
30
|
return existsSync(join(projectRoot, "storage/framework/core")) && existsSync(join(projectRoot, "node_modules/@stacksjs/env/src/index.ts")) ? "--conditions development" : "";
|
|
31
31
|
}
|
|
32
|
-
async function resolveActionFile(action) {
|
|
32
|
+
async function resolveActionFile(action, projectRoot) {
|
|
33
33
|
const candidates = [];
|
|
34
|
+
if (projectRoot)
|
|
35
|
+
candidates.push(join(projectRoot, `storage/framework/core/actions/src/${action}.ts`));
|
|
34
36
|
candidates.push(p.actionsPath(`src/${action}.ts`));
|
|
35
37
|
candidates.push(...publishedActionCandidates(action));
|
|
36
38
|
for (const candidate of candidates)
|
|
@@ -60,8 +62,9 @@ export async function runAction(action, options) {
|
|
|
60
62
|
} catch (error) {
|
|
61
63
|
return err(`Failed to start dev server: ${error}`);
|
|
62
64
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
+
const isLikelyCoreAction = action.includes("/") || ["dev", "build", "install", "upgrade", "migrate"].some((prefix) => action.startsWith(prefix)), userActionsBase = options?.cwd ? join(String(options.cwd), "app/Actions") : p.userActionsPath();
|
|
66
|
+
if (!isLikelyCoreAction && existsSync(userActionsBase)) {
|
|
67
|
+
const glob = new Bun.Glob("**/*.{ts,js}"), scanOptions = { cwd: userActionsBase, onlyFiles: !0, absolute: !0 }, matchingFiles = [], basePath = userActionsBase;
|
|
65
68
|
for await (const file of glob.scan(scanOptions)) {
|
|
66
69
|
if (file.replace(`${basePath}/`, "").replace(/\.(ts|js)$/, "") === action || file.endsWith(`${action}.ts`) || file.endsWith(`${action}.js`)) {
|
|
67
70
|
log.debug(`[action] Resolved: ${action} \u2192 ${file}`);
|
|
@@ -78,7 +81,7 @@ export async function runAction(action, options) {
|
|
|
78
81
|
}
|
|
79
82
|
} catch (error) {}
|
|
80
83
|
}
|
|
81
|
-
const path = await resolveActionFile(action);
|
|
84
|
+
const path = await resolveActionFile(action, options?.cwd ? String(options.cwd) : void 0);
|
|
82
85
|
if (!path)
|
|
83
86
|
return err(`Action '${action}' not found in storage/framework/core/actions/src or @stacksjs/actions`);
|
|
84
87
|
log.debug(`[action] Resolved: ${action} \u2192 ${path}`);
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Every dependency name a manifest pins to `catalog:`.
|
|
3
|
+
*
|
|
4
|
+
* Bun also accepts the named form (`catalog:react19`); those resolve from a
|
|
5
|
+
* separate `catalogs` map rather than the default catalog, so we deliberately
|
|
6
|
+
* match only the bare default protocol here.
|
|
7
|
+
*/
|
|
8
|
+
export declare function collectCatalogReferences(manifests: PackageManifest[]): string[];
|
|
9
|
+
/**
|
|
10
|
+
* Read a catalog off a manifest, accepting either shape Bun supports: a
|
|
11
|
+
* top-level `catalog` field, or `workspaces.catalog` on the object form.
|
|
12
|
+
*/
|
|
13
|
+
export declare function readCatalogField(manifest: PackageManifest | null | undefined): Record<string, string>;
|
|
14
|
+
/**
|
|
15
|
+
* Merge the upstream catalog into the app's own, restricted to what the
|
|
16
|
+
* vendored tree references.
|
|
17
|
+
*
|
|
18
|
+
* User-authored entries survive untouched: we only write keys that appear in
|
|
19
|
+
* `referenced`, so a catalog entry the app added for its own packages is never
|
|
20
|
+
* clobbered by an upgrade.
|
|
21
|
+
*/
|
|
22
|
+
export declare function mergeCatalog(current: Record<string, string>, upstream: Record<string, string>, referenced: string[]): CatalogMergeResult;
|
|
23
|
+
/** Parse a `package.json`, returning null rather than throwing on bad input. */
|
|
24
|
+
export declare function readManifest(path: string): PackageManifest | null;
|
|
25
|
+
/**
|
|
26
|
+
* Walk a synced tree for workspace manifests. Depth is bounded because the
|
|
27
|
+
* framework nests packages at most a few levels deep, and an unbounded walk
|
|
28
|
+
* over a vendored tree is needlessly expensive on every upgrade.
|
|
29
|
+
*/
|
|
30
|
+
export declare function findWorkspaceManifests(root: string, maxDepth?: number): string[];
|
|
31
|
+
/**
|
|
32
|
+
* Fetch the upstream catalog for a GitHub-sourced upgrade.
|
|
33
|
+
*
|
|
34
|
+
* We hit the raw root manifest directly rather than re-downloading a tarball
|
|
35
|
+
* we've already extracted the interesting parts of. Failure is non-fatal: the
|
|
36
|
+
* caller falls back to leaving the existing catalog alone.
|
|
37
|
+
*/
|
|
38
|
+
export declare function fetchUpstreamCatalog(ref: string): Promise<Record<string, string>>;
|
|
39
|
+
/**
|
|
40
|
+
* Mirror the upstream catalog into the app's root manifest so the vendored
|
|
41
|
+
* workspace members resolve. Returns null when there is nothing to do.
|
|
42
|
+
*/
|
|
43
|
+
export declare function reconcileWorkspaceCatalog(options: ReconcileCatalogOptions): Promise<CatalogMergeResult | null>;
|
|
44
|
+
export declare interface PackageManifest {
|
|
45
|
+
catalog?: Record<string, string>
|
|
46
|
+
workspaces?: string[] | { catalog?: Record<string, string> }
|
|
47
|
+
[field: string]: unknown
|
|
48
|
+
}
|
|
49
|
+
export declare interface CatalogMergeResult {
|
|
50
|
+
catalog: Record<string, string>
|
|
51
|
+
added: string[]
|
|
52
|
+
updated: string[]
|
|
53
|
+
missing: string[]
|
|
54
|
+
}
|
|
55
|
+
export declare interface ReconcileCatalogOptions {
|
|
56
|
+
projectRoot: string
|
|
57
|
+
frameworkRoot: string
|
|
58
|
+
localStacksRoot?: string | null
|
|
59
|
+
ref?: string
|
|
60
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
const CATALOG_PROTOCOL = "catalog:", DEPENDENCY_FIELDS = [
|
|
4
|
+
"dependencies",
|
|
5
|
+
"devDependencies",
|
|
6
|
+
"peerDependencies",
|
|
7
|
+
"optionalDependencies"
|
|
8
|
+
], MANIFEST_SCAN_SKIP = new Set(["node_modules", "dist", ".git", ".cache", "cache"]);
|
|
9
|
+
export function collectCatalogReferences(manifests) {
|
|
10
|
+
const names = new Set;
|
|
11
|
+
for (const manifest of manifests)
|
|
12
|
+
for (const field of DEPENDENCY_FIELDS) {
|
|
13
|
+
const deps = manifest[field];
|
|
14
|
+
if (!deps || typeof deps !== "object")
|
|
15
|
+
continue;
|
|
16
|
+
for (const [name, range] of Object.entries(deps))
|
|
17
|
+
if (range === CATALOG_PROTOCOL)
|
|
18
|
+
names.add(name);
|
|
19
|
+
}
|
|
20
|
+
return [...names].sort();
|
|
21
|
+
}
|
|
22
|
+
export function readCatalogField(manifest) {
|
|
23
|
+
if (!manifest)
|
|
24
|
+
return {};
|
|
25
|
+
if (manifest.catalog && typeof manifest.catalog === "object")
|
|
26
|
+
return { ...manifest.catalog };
|
|
27
|
+
const workspaces = manifest.workspaces;
|
|
28
|
+
if (workspaces && !Array.isArray(workspaces) && workspaces.catalog)
|
|
29
|
+
return { ...workspaces.catalog };
|
|
30
|
+
return {};
|
|
31
|
+
}
|
|
32
|
+
export function mergeCatalog(current, upstream, referenced) {
|
|
33
|
+
const catalog = { ...current }, added = [], updated = [], missing = [];
|
|
34
|
+
for (const name of referenced) {
|
|
35
|
+
const version = upstream[name];
|
|
36
|
+
if (!version) {
|
|
37
|
+
if (!(name in catalog))
|
|
38
|
+
missing.push(name);
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
if (!(name in catalog)) {
|
|
42
|
+
catalog[name] = version;
|
|
43
|
+
added.push(name);
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
if (catalog[name] !== version) {
|
|
47
|
+
catalog[name] = version;
|
|
48
|
+
updated.push(name);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return { catalog: sortKeys(catalog), added, updated, missing };
|
|
52
|
+
}
|
|
53
|
+
function sortKeys(record) {
|
|
54
|
+
return Object.fromEntries(Object.entries(record).sort(([a], [b]) => a.localeCompare(b)));
|
|
55
|
+
}
|
|
56
|
+
export function readManifest(path) {
|
|
57
|
+
try {
|
|
58
|
+
if (!existsSync(path))
|
|
59
|
+
return null;
|
|
60
|
+
return JSON.parse(readFileSync(path, "utf-8"));
|
|
61
|
+
} catch {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
export function findWorkspaceManifests(root, maxDepth = 4) {
|
|
66
|
+
const found = [], walk = (dir, depth) => {
|
|
67
|
+
if (depth > maxDepth || !existsSync(dir))
|
|
68
|
+
return;
|
|
69
|
+
const manifest = join(dir, "package.json");
|
|
70
|
+
if (existsSync(manifest))
|
|
71
|
+
found.push(manifest);
|
|
72
|
+
let entries;
|
|
73
|
+
try {
|
|
74
|
+
entries = readdirSync(dir, { withFileTypes: !0 });
|
|
75
|
+
} catch {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
for (const entry of entries) {
|
|
79
|
+
if (!entry.isDirectory())
|
|
80
|
+
continue;
|
|
81
|
+
if (entry.name.startsWith(".") || MANIFEST_SCAN_SKIP.has(entry.name))
|
|
82
|
+
continue;
|
|
83
|
+
walk(join(dir, entry.name), depth + 1);
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
walk(root, 0);
|
|
87
|
+
return found;
|
|
88
|
+
}
|
|
89
|
+
export async function fetchUpstreamCatalog(ref) {
|
|
90
|
+
try {
|
|
91
|
+
const response = await fetch(`https://raw.githubusercontent.com/stacksjs/stacks/${ref}/package.json`);
|
|
92
|
+
if (!response.ok)
|
|
93
|
+
return {};
|
|
94
|
+
return readCatalogField(await response.json());
|
|
95
|
+
} catch {
|
|
96
|
+
return {};
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
export async function reconcileWorkspaceCatalog(options) {
|
|
100
|
+
const rootManifestPath = join(options.projectRoot, "package.json"), rootManifest = readManifest(rootManifestPath);
|
|
101
|
+
if (!rootManifest)
|
|
102
|
+
return null;
|
|
103
|
+
const manifests = findWorkspaceManifests(options.frameworkRoot).map(readManifest).filter((manifest) => manifest !== null), referenced = collectCatalogReferences(manifests);
|
|
104
|
+
if (referenced.length === 0)
|
|
105
|
+
return null;
|
|
106
|
+
const upstream = options.localStacksRoot ? readCatalogField(readManifest(join(options.localStacksRoot, "package.json"))) : await fetchUpstreamCatalog(options.ref || "main"), result = mergeCatalog(readCatalogField(rootManifest), upstream, referenced);
|
|
107
|
+
if (result.added.length === 0 && result.updated.length === 0)
|
|
108
|
+
return result;
|
|
109
|
+
rootManifest.catalog = result.catalog;
|
|
110
|
+
await Bun.write(rootManifestPath, `${JSON.stringify(rootManifest, null, 2)}
|
|
111
|
+
`);
|
|
112
|
+
return result;
|
|
113
|
+
}
|
|
@@ -1,3 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A `pantry.lock` is the marker: it only exists once pantry owns the graph.
|
|
3
|
+
*
|
|
4
|
+
* Getting this wrong is not cosmetic. Running `bun install` in a pantry app
|
|
5
|
+
* refreshes a `node_modules` tree that nothing resolves against, so the
|
|
6
|
+
* upgrade reports success while the app keeps importing the stale copies in
|
|
7
|
+
* `pantry/` - which surfaces later as missing exports from packages the
|
|
8
|
+
* manifest claims are current.
|
|
9
|
+
*/
|
|
10
|
+
export declare function detectDependencyInstaller(hasPantryLock: boolean): DependencyInstaller;
|
|
11
|
+
/**
|
|
12
|
+
* Build the refresh argv. Both installers spell the "re-resolve everything"
|
|
13
|
+
* flag `--force`, so the shape is the same either way.
|
|
14
|
+
*/
|
|
15
|
+
export declare function resolveDependencyRefreshCommand(options: {
|
|
16
|
+
installer: DependencyInstaller
|
|
17
|
+
bunExecutable: string
|
|
18
|
+
pantryExecutable: string
|
|
19
|
+
}): string[];
|
|
20
|
+
/**
|
|
21
|
+
* Pick the first pantry binary that exists, falling back to a bare `pantry`
|
|
22
|
+
* so a PATH install still works. `exists` is injected to keep this testable.
|
|
23
|
+
*/
|
|
24
|
+
export declare function resolvePantryExecutable(candidates: string[], exists: (path: string) => boolean): string;
|
|
1
25
|
/**
|
|
2
26
|
* The first upgrade process records the real file changes, then restarts itself
|
|
3
27
|
* after replacing its own framework code. The restarted process sees the
|
|
@@ -14,8 +38,8 @@ export declare function shouldRefreshPostSyncDependencies(corePackageChanged: bo
|
|
|
14
38
|
/**
|
|
15
39
|
* Fully refresh dependencies after replacing vendored workspace manifests.
|
|
16
40
|
*
|
|
17
|
-
* A plain
|
|
18
|
-
*
|
|
41
|
+
* A plain install may reuse the existing nested package placement and leave a
|
|
42
|
+
* lockfile that changes in a clean checkout. `--force` makes the installer
|
|
19
43
|
* resolve the complete workspace graph so the resulting lockfile is valid for
|
|
20
44
|
* subsequent frozen installs.
|
|
21
45
|
*/
|
|
@@ -45,8 +69,17 @@ declare interface PostSyncMigrationOptions {
|
|
|
45
69
|
spawn: PostSyncSpawn
|
|
46
70
|
}
|
|
47
71
|
declare interface PostSyncDependencyOptions {
|
|
48
|
-
|
|
72
|
+
cmd: string[]
|
|
49
73
|
projectRoot: string
|
|
50
74
|
spawn: PostSyncSpawn
|
|
51
75
|
}
|
|
52
76
|
declare type PostSyncSpawn = (options: PostSyncSpawnOptions) => { exited: Promise<number> }
|
|
77
|
+
/**
|
|
78
|
+
* Which installer owns the project's dependency graph.
|
|
79
|
+
*
|
|
80
|
+
* Stacks apps come in two flavours. The default resolves third-party packages
|
|
81
|
+
* from `node_modules` and is installed by Bun. A pantry app instead installs
|
|
82
|
+
* into `pantry/` and points tsconfig's catch-all path mapping at it, so that
|
|
83
|
+
* directory - not `node_modules` - is what module resolution actually reads.
|
|
84
|
+
*/
|
|
85
|
+
export type DependencyInstaller = 'bun' | 'pantry';
|
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
export function detectDependencyInstaller(hasPantryLock) {
|
|
2
|
+
return hasPantryLock ? "pantry" : "bun";
|
|
3
|
+
}
|
|
4
|
+
export function resolveDependencyRefreshCommand(options) {
|
|
5
|
+
return [options.installer === "pantry" ? options.pantryExecutable : options.bunExecutable, "install", "--force"];
|
|
6
|
+
}
|
|
7
|
+
export function resolvePantryExecutable(candidates, exists) {
|
|
8
|
+
for (const candidate of candidates)
|
|
9
|
+
if (candidate && exists(candidate))
|
|
10
|
+
return candidate;
|
|
11
|
+
return "pantry";
|
|
12
|
+
}
|
|
1
13
|
export function shouldRunPostSyncHooks(changeCount, alreadyRestarted) {
|
|
2
14
|
return changeCount > 0 || alreadyRestarted;
|
|
3
15
|
}
|
|
@@ -6,7 +18,7 @@ export function shouldRefreshPostSyncDependencies(corePackageChanged, alreadyRes
|
|
|
6
18
|
}
|
|
7
19
|
export async function runPostSyncDependencyRefresh(options) {
|
|
8
20
|
const code = await options.spawn({
|
|
9
|
-
cmd:
|
|
21
|
+
cmd: options.cmd,
|
|
10
22
|
cwd: options.projectRoot,
|
|
11
23
|
stdin: "ignore",
|
|
12
24
|
stdout: "inherit",
|
|
@@ -2,17 +2,35 @@
|
|
|
2
2
|
* Resolve the target channel and git ref from CLI options and persisted channel.
|
|
3
3
|
* Priority: --version > --canary > --stable > persisted channel
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
* is
|
|
7
|
-
*
|
|
8
|
-
* channel tracks the `main` branch (bleeding
|
|
9
|
-
*
|
|
5
|
+
* Release model: `main` is the bleeding-edge "dump everything" branch, and each
|
|
6
|
+
* release is cut as a `vX.Y.Z` git tag (see `.github/workflows/release.yml`,
|
|
7
|
+
* which triggers on `push: tags: ['v*']`). There is deliberately no long-lived
|
|
8
|
+
* `stable` branch. So the `canary` channel tracks the `main` branch (bleeding
|
|
9
|
+
* edge) and the `stable` channel tracks the latest published `vX.Y.Z` tag —
|
|
10
|
+
* passed in as `latestStableRef` because resolving it needs the network, which
|
|
11
|
+
* this pure function stays free of. A pinned `--version` resolves directly to
|
|
12
|
+
* the matching `vX` tag.
|
|
10
13
|
*/
|
|
11
14
|
export declare function resolveUpgradeContext(options: {
|
|
12
15
|
version?: string
|
|
13
16
|
canary?: boolean
|
|
14
17
|
stable?: boolean
|
|
15
|
-
}, currentChannel: 'stable' | 'canary'): UpgradeContext;
|
|
18
|
+
}, currentChannel: 'stable' | 'canary', latestStableRef: string): UpgradeContext;
|
|
19
|
+
/**
|
|
20
|
+
* Pick the highest stable release tag from a list of tag names. Only clean
|
|
21
|
+
* `vMAJOR.MINOR.PATCH` tags qualify — pre-releases (`v1.0.0-beta.1`), annotated
|
|
22
|
+
* deref entries (`…^{}`), and non-version tags are ignored. Returns null when
|
|
23
|
+
* nothing qualifies.
|
|
24
|
+
*/
|
|
25
|
+
export declare function pickLatestStableTag(tags: string[]): string | null;
|
|
26
|
+
/**
|
|
27
|
+
* Resolve the latest published stable release tag (e.g. `"v0.70.163"`) by
|
|
28
|
+
* listing the remote's `v*` tags. The stable channel installs this tag rather
|
|
29
|
+
* than a branch, because releases are cut as tags and no `stable` branch
|
|
30
|
+
* exists. Returns null if the tags can't be listed (offline / no git), letting
|
|
31
|
+
* the caller fall back to the currently-installed version.
|
|
32
|
+
*/
|
|
33
|
+
export declare function resolveLatestStableTag(): Promise<string | null>;
|
|
16
34
|
/**
|
|
17
35
|
* Build the gitit template string for a path under the stacks repo.
|
|
18
36
|
* `subPath` is relative to the repo root (e.g. `storage/framework/core`).
|
|
@@ -2,17 +2,49 @@ import { existsSync, readdirSync, readFileSync, statSync, writeFileSync } from "
|
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { join, resolve } from "node:path";
|
|
4
4
|
import process from "node:process";
|
|
5
|
-
export function resolveUpgradeContext(options, currentChannel) {
|
|
5
|
+
export function resolveUpgradeContext(options, currentChannel, latestStableRef) {
|
|
6
6
|
const targetVersion = options.version;
|
|
7
7
|
if (targetVersion)
|
|
8
8
|
return { channel: "stable", ref: targetVersion.startsWith("v") ? targetVersion : `v${targetVersion}`, targetVersion };
|
|
9
9
|
if (options.canary)
|
|
10
10
|
return { channel: "canary", ref: "main" };
|
|
11
11
|
if (options.stable)
|
|
12
|
-
return { channel: "stable", ref:
|
|
12
|
+
return { channel: "stable", ref: latestStableRef };
|
|
13
13
|
if (currentChannel === "canary")
|
|
14
14
|
return { channel: "canary", ref: "main" };
|
|
15
|
-
return { channel: "stable", ref:
|
|
15
|
+
return { channel: "stable", ref: latestStableRef };
|
|
16
|
+
}
|
|
17
|
+
function compareVersionParts(a, b) {
|
|
18
|
+
return a[0] - b[0] || a[1] - b[1] || a[2] - b[2];
|
|
19
|
+
}
|
|
20
|
+
export function pickLatestStableTag(tags) {
|
|
21
|
+
const re = /^v(\d+)\.(\d+)\.(\d+)$/;
|
|
22
|
+
let best = null;
|
|
23
|
+
for (const tag of tags) {
|
|
24
|
+
const m = re.exec(tag.trim());
|
|
25
|
+
if (!m)
|
|
26
|
+
continue;
|
|
27
|
+
const parts = [Number(m[1]), Number(m[2]), Number(m[3])];
|
|
28
|
+
if (!best || compareVersionParts(parts, best.parts) > 0)
|
|
29
|
+
best = { tag: tag.trim(), parts };
|
|
30
|
+
}
|
|
31
|
+
return best?.tag ?? null;
|
|
32
|
+
}
|
|
33
|
+
export async function resolveLatestStableTag() {
|
|
34
|
+
try {
|
|
35
|
+
const proc = Bun.spawn({
|
|
36
|
+
cmd: ["git", "ls-remote", "--tags", "--refs", "https://github.com/stacksjs/stacks.git", "v*"],
|
|
37
|
+
stdout: "pipe",
|
|
38
|
+
stderr: "pipe"
|
|
39
|
+
}), out = await new Response(proc.stdout).text();
|
|
40
|
+
if (await proc.exited !== 0)
|
|
41
|
+
return null;
|
|
42
|
+
const tags = out.split(`
|
|
43
|
+
`).map((line) => line.split("\t")[1]?.replace("refs/tags/", "")).filter((t) => !!t);
|
|
44
|
+
return pickLatestStableTag(tags);
|
|
45
|
+
} catch {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
16
48
|
}
|
|
17
49
|
export function buildTemplateString(ref, subPath = "storage/framework/core") {
|
|
18
50
|
return `github:stacksjs/stacks/${subPath}#${ref}`;
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
readChannel,
|
|
16
16
|
readSyncedVersion,
|
|
17
17
|
readVersion,
|
|
18
|
+
resolveLatestStableTag,
|
|
18
19
|
resolveSuccessMessage,
|
|
19
20
|
resolveUpgradeContext,
|
|
20
21
|
resolveUpgradeMessage,
|
|
@@ -25,7 +26,11 @@ import {
|
|
|
25
26
|
writeChannel,
|
|
26
27
|
writeSyncedVersion
|
|
27
28
|
} from "./framework-utils";
|
|
29
|
+
import { reconcileWorkspaceCatalog } from "./catalog";
|
|
28
30
|
import {
|
|
31
|
+
detectDependencyInstaller,
|
|
32
|
+
resolveDependencyRefreshCommand,
|
|
33
|
+
resolvePantryExecutable,
|
|
29
34
|
runPostSyncDependencyRefresh,
|
|
30
35
|
runPostSyncMigration,
|
|
31
36
|
shouldRefreshPostSyncDependencies,
|
|
@@ -43,7 +48,11 @@ if (!existsSync(p.projectPath("storage/framework/core"))) {
|
|
|
43
48
|
noPostinstall: options.noPostinstall ?? options.postinstall === !1
|
|
44
49
|
});
|
|
45
50
|
}
|
|
46
|
-
const channelFile = join(projectRoot, ".stacks-channel"), versionFile = join(projectRoot, ".stacks-version"), currentChannel = readChannel(channelFile),
|
|
51
|
+
const channelFile = join(projectRoot, ".stacks-channel"), versionFile = join(projectRoot, ".stacks-version"), currentChannel = readChannel(channelFile), corePkgPath = p.projectPath("storage/framework/core/buddy/package.json"), currentVersion = readVersion(corePkgPath), willResolveStable = !options.version && !options.canary && (!!options.stable || currentChannel !== "canary");
|
|
52
|
+
let latestStableRef = "";
|
|
53
|
+
if (willResolveStable)
|
|
54
|
+
latestStableRef = await resolveLatestStableTag() ?? (currentVersion ? `v${currentVersion}` : "main");
|
|
55
|
+
const ctx = resolveUpgradeContext(options, currentChannel, latestStableRef), explicitLocal = options.from ? p.projectPath(options.from) : null, detectedLocal = explicitLocal ?? (shouldAutoDetectLocalStacks(options) ? detectLocalStacks(projectRoot) : null), usingLocal = !!detectedLocal;
|
|
47
56
|
console.log(resolveUpgradeMessage(ctx, currentChannel, !!options.stable));
|
|
48
57
|
console.log(usingLocal ? ` source: local checkout at ${detectedLocal}` : ` source: github:stacksjs/stacks#${ctx.ref}`);
|
|
49
58
|
if (options.dryRun) {
|
|
@@ -130,7 +139,7 @@ for (const { managed, summary } of perPath)
|
|
|
130
139
|
const runHooks = !options.noPostinstall && options.postinstall !== !1;
|
|
131
140
|
if (runHooks)
|
|
132
141
|
try {
|
|
133
|
-
await runPostSyncHooks({ aggregate, perPath, projectRoot, alreadyRestarted });
|
|
142
|
+
await runPostSyncHooks({ aggregate, perPath, projectRoot, alreadyRestarted, localStacksRoot: detectedLocal, ref: ctx.ref });
|
|
134
143
|
} catch (err) {
|
|
135
144
|
console.error(`Post-sync hooks failed: ${err?.message || err}`);
|
|
136
145
|
process.exit(ExitCode.FatalError);
|
|
@@ -303,13 +312,21 @@ async function getRemoteSha(context, fromLocal, stacksRoot) {
|
|
|
303
312
|
return await head.exited === 0 && SHA_RE.test(h) ? h.toLowerCase() : null;
|
|
304
313
|
}
|
|
305
314
|
const proc = Bun.spawn({
|
|
306
|
-
cmd: [
|
|
315
|
+
cmd: [
|
|
316
|
+
"git",
|
|
317
|
+
"ls-remote",
|
|
318
|
+
"https://github.com/stacksjs/stacks.git",
|
|
319
|
+
`refs/heads/${context.ref}`,
|
|
320
|
+
`refs/tags/${context.ref}`,
|
|
321
|
+
`refs/tags/${context.ref}^{}`
|
|
322
|
+
],
|
|
307
323
|
stdout: "pipe",
|
|
308
324
|
stderr: "pipe"
|
|
309
325
|
}), out = await new Response(proc.stdout).text();
|
|
310
326
|
if (await proc.exited !== 0)
|
|
311
327
|
return null;
|
|
312
|
-
const
|
|
328
|
+
const lines = out.split(`
|
|
329
|
+
`).filter(Boolean), m = (lines.find((l) => l.includes("^{}")) ?? lines[0])?.match(/^([0-9a-f]{40})\s/i);
|
|
313
330
|
return m?.[1] ? m[1].toLowerCase() : null;
|
|
314
331
|
} catch {
|
|
315
332
|
return null;
|
|
@@ -400,7 +417,7 @@ async function syncRootFilesFromGitHub(ref) {
|
|
|
400
417
|
}
|
|
401
418
|
}
|
|
402
419
|
async function runPostSyncHooks(args) {
|
|
403
|
-
const { aggregate, perPath, projectRoot, alreadyRestarted } = args, changeCount = aggregate.added + aggregate.changed + aggregate.removed;
|
|
420
|
+
const { aggregate, perPath, projectRoot, alreadyRestarted, localStacksRoot, ref } = args, changeCount = aggregate.added + aggregate.changed + aggregate.removed;
|
|
404
421
|
if (!shouldRunPostSyncHooks(changeCount, alreadyRestarted)) {
|
|
405
422
|
console.log("Nothing changed \u2014 skipping post-sync hooks.");
|
|
406
423
|
return;
|
|
@@ -412,6 +429,25 @@ async function runPostSyncHooks(args) {
|
|
|
412
429
|
} catch (err) {
|
|
413
430
|
console.warn(` auto-imports failed (non-fatal): ${err?.message || err}`);
|
|
414
431
|
}
|
|
432
|
+
console.log("Reconciling workspace dependency catalog...");
|
|
433
|
+
try {
|
|
434
|
+
const result = await reconcileWorkspaceCatalog({
|
|
435
|
+
projectRoot,
|
|
436
|
+
frameworkRoot: join(projectRoot, "storage", "framework"),
|
|
437
|
+
localStacksRoot,
|
|
438
|
+
ref
|
|
439
|
+
});
|
|
440
|
+
if (!result)
|
|
441
|
+
console.log(" catalog: nothing to reconcile");
|
|
442
|
+
else if (result.added.length || result.updated.length)
|
|
443
|
+
console.log(` catalog: +${result.added.length} added, ~${result.updated.length} updated`);
|
|
444
|
+
else
|
|
445
|
+
console.log(" catalog: up to date");
|
|
446
|
+
if (result?.missing.length)
|
|
447
|
+
console.warn(` catalog: no upstream version for ${result.missing.join(", ")}`);
|
|
448
|
+
} catch (err) {
|
|
449
|
+
console.warn(` catalog reconcile failed (non-fatal): ${err?.message || err}`);
|
|
450
|
+
}
|
|
415
451
|
if (shouldRefreshPostSyncDependencies(perPath.some((entry) => {
|
|
416
452
|
const { managed, summary } = entry;
|
|
417
453
|
if (managed.isFile)
|
|
@@ -422,15 +458,20 @@ async function runPostSyncHooks(args) {
|
|
|
422
458
|
return !1;
|
|
423
459
|
return pkgJsonInTree(join(projectRoot, managed.localPath));
|
|
424
460
|
}), alreadyRestarted)) {
|
|
425
|
-
|
|
461
|
+
const installer = detectDependencyInstaller(existsSync(join(projectRoot, "pantry.lock"))), cmd = resolveDependencyRefreshCommand({
|
|
462
|
+
installer,
|
|
463
|
+
bunExecutable: process.argv[0] || "bun",
|
|
464
|
+
pantryExecutable: resolvePantryExecutable(pantryExecutableCandidates(projectRoot), existsSync)
|
|
465
|
+
});
|
|
466
|
+
console.log(`Running \`${installer} install\` to refresh dependencies...`);
|
|
426
467
|
try {
|
|
427
468
|
await runPostSyncDependencyRefresh({
|
|
428
|
-
|
|
469
|
+
cmd,
|
|
429
470
|
projectRoot,
|
|
430
471
|
spawn: (spawnOptions) => Bun.spawn(spawnOptions)
|
|
431
472
|
});
|
|
432
473
|
} catch (err) {
|
|
433
|
-
console.warn(`
|
|
474
|
+
console.warn(` ${installer} install failed (non-fatal): ${err?.message || err}`);
|
|
434
475
|
}
|
|
435
476
|
}
|
|
436
477
|
const migrationsDir = join(projectRoot, "database", "migrations");
|
|
@@ -445,6 +486,14 @@ async function runPostSyncHooks(args) {
|
|
|
445
486
|
});
|
|
446
487
|
}
|
|
447
488
|
}
|
|
489
|
+
function pantryExecutableCandidates(projectRoot) {
|
|
490
|
+
const home = process.env.HOME ?? "";
|
|
491
|
+
return [
|
|
492
|
+
join(projectRoot, "pantry", ".bin", "pantry"),
|
|
493
|
+
home ? join(home, ".local", "bin", "pantry") : "",
|
|
494
|
+
home ? join(home, ".local", "share", "pantry", "global", "bin", "pantry") : ""
|
|
495
|
+
].filter(Boolean);
|
|
496
|
+
}
|
|
448
497
|
function pkgJsonInTree(dir) {
|
|
449
498
|
try {
|
|
450
499
|
if (!existsSync(dir))
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@stacksjs/actions",
|
|
3
3
|
"type": "module",
|
|
4
4
|
"sideEffects": false,
|
|
5
|
-
"version": "0.70.
|
|
5
|
+
"version": "0.70.164",
|
|
6
6
|
"description": "The Stacks actions.",
|
|
7
7
|
"author": "Chris Breuer",
|
|
8
8
|
"contributors": [
|
|
@@ -62,32 +62,32 @@
|
|
|
62
62
|
"prepublishOnly": "bun run build"
|
|
63
63
|
},
|
|
64
64
|
"dependencies": {
|
|
65
|
-
"@stacksjs/config": "0.70.
|
|
65
|
+
"@stacksjs/config": "0.70.164",
|
|
66
66
|
"@stacksjs/bumpx": "^0.2.6",
|
|
67
67
|
"@stacksjs/bunpress": "^0.1.18",
|
|
68
68
|
"@stacksjs/logsmith": "^0.2.3",
|
|
69
|
-
"@stacksjs/registry": "0.70.
|
|
70
|
-
"@stacksjs/ts-cloud": "^0.7.
|
|
69
|
+
"@stacksjs/registry": "0.70.164",
|
|
70
|
+
"@stacksjs/ts-cloud": "^0.7.60",
|
|
71
71
|
"@stacksjs/ts-md": "^0.1.1"
|
|
72
72
|
},
|
|
73
73
|
"devDependencies": {
|
|
74
|
-
"@stacksjs/api": "0.70.
|
|
75
|
-
"@stacksjs/cli": "0.70.
|
|
76
|
-
"@stacksjs/config": "0.70.
|
|
77
|
-
"@stacksjs/database": "0.70.
|
|
74
|
+
"@stacksjs/api": "0.70.164",
|
|
75
|
+
"@stacksjs/cli": "0.70.164",
|
|
76
|
+
"@stacksjs/config": "0.70.164",
|
|
77
|
+
"@stacksjs/database": "0.70.164",
|
|
78
78
|
"@stacksjs/tlsx": "^0.13.2",
|
|
79
79
|
"better-dx": "^0.2.17",
|
|
80
|
-
"@stacksjs/dns": "0.70.
|
|
81
|
-
"@stacksjs/enums": "0.70.
|
|
82
|
-
"@stacksjs/env": "0.70.
|
|
83
|
-
"@stacksjs/error-handling": "0.70.
|
|
84
|
-
"@stacksjs/logging": "0.70.
|
|
85
|
-
"@stacksjs/path": "0.70.
|
|
86
|
-
"@stacksjs/security": "0.70.
|
|
87
|
-
"@stacksjs/storage": "0.70.
|
|
88
|
-
"@stacksjs/strings": "0.70.
|
|
89
|
-
"@stacksjs/utils": "0.70.
|
|
90
|
-
"@stacksjs/validation": "0.70.
|
|
80
|
+
"@stacksjs/dns": "0.70.164",
|
|
81
|
+
"@stacksjs/enums": "0.70.164",
|
|
82
|
+
"@stacksjs/env": "0.70.164",
|
|
83
|
+
"@stacksjs/error-handling": "0.70.164",
|
|
84
|
+
"@stacksjs/logging": "0.70.164",
|
|
85
|
+
"@stacksjs/path": "0.70.164",
|
|
86
|
+
"@stacksjs/security": "0.70.164",
|
|
87
|
+
"@stacksjs/storage": "0.70.164",
|
|
88
|
+
"@stacksjs/strings": "0.70.164",
|
|
89
|
+
"@stacksjs/utils": "0.70.164",
|
|
90
|
+
"@stacksjs/validation": "0.70.164"
|
|
91
91
|
},
|
|
92
92
|
"peerDependencies": {
|
|
93
93
|
"pickier": "^0.1.35"
|