@pgpmjs/core 7.29.0 → 7.31.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.
@@ -103,5 +103,37 @@ export type TemplateRepoFlavor = keyof typeof TEMPLATE_REPOS;
103
103
  export declare const DEFAULT_TEMPLATE_REPO: "https://github.com/constructive-io/pgpm-boilerplates.git";
104
104
  export declare const DEFAULT_TEMPLATE_TTL_MS: number;
105
105
  export declare const DEFAULT_TEMPLATE_TOOL_NAME = "pgpm";
106
+ export interface TemplateSourceInfo {
107
+ repo: string;
108
+ branch?: string;
109
+ remoteSha?: string;
110
+ refreshed: boolean;
111
+ offline: boolean;
112
+ local: boolean;
113
+ }
114
+ export interface RefreshTemplateCacheOptions {
115
+ templateRepo?: string;
116
+ branch?: string;
117
+ toolName?: string;
118
+ cacheBaseDir?: string;
119
+ cwd?: string;
120
+ force?: boolean;
121
+ }
122
+ /**
123
+ * Clear the per-process refresh memo. Primarily useful for tests and callers
124
+ * that begin a new init run in the same process.
125
+ */
126
+ export declare function _resetTemplateRefreshMemo(): void;
127
+ /**
128
+ * Check the remote template revision and clear a stale local clone.
129
+ *
130
+ * Set PGPM_TEMPLATE_OFFLINE to skip `git ls-remote`, which is useful for tests
131
+ * and environments that must not make network requests.
132
+ */
133
+ export declare function refreshTemplateCache(options?: RefreshTemplateCacheOptions): TemplateSourceInfo;
134
+ export declare function describeTemplateSource(info: TemplateSourceInfo, opts?: {
135
+ toolName?: string;
136
+ cacheBaseDir?: string;
137
+ }): string;
106
138
  export declare function inspectTemplate(options: InspectTemplateOptions): InspectTemplateResult;
107
139
  export declare function scaffoldTemplate(options: ScaffoldTemplateOptions): Promise<ScaffoldTemplateResult>;
@@ -4,8 +4,13 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.DEFAULT_TEMPLATE_TOOL_NAME = exports.DEFAULT_TEMPLATE_TTL_MS = exports.DEFAULT_TEMPLATE_REPO = exports.TEMPLATE_REPOS = exports.SkillInstaller = void 0;
7
+ exports._resetTemplateRefreshMemo = _resetTemplateRefreshMemo;
8
+ exports.refreshTemplateCache = refreshTemplateCache;
9
+ exports.describeTemplateSource = describeTemplateSource;
7
10
  exports.inspectTemplate = inspectTemplate;
8
11
  exports.scaffoldTemplate = scaffoldTemplate;
12
+ const child_process_1 = require("child_process");
13
+ const fs_1 = __importDefault(require("fs"));
9
14
  const genomic_1 = require("genomic");
10
15
  const os_1 = __importDefault(require("os"));
11
16
  const path_1 = __importDefault(require("path"));
@@ -35,6 +40,150 @@ function resolveCacheBaseDir(cacheBaseDir) {
35
40
  }
36
41
  return undefined;
37
42
  }
43
+ const templateRefreshMemo = new Map();
44
+ /**
45
+ * Clear the per-process refresh memo. Primarily useful for tests and callers
46
+ * that begin a new init run in the same process.
47
+ */
48
+ function _resetTemplateRefreshMemo() {
49
+ templateRefreshMemo.clear();
50
+ }
51
+ /**
52
+ * Check the remote template revision and clear a stale local clone.
53
+ *
54
+ * Set PGPM_TEMPLATE_OFFLINE to skip `git ls-remote`, which is useful for tests
55
+ * and environments that must not make network requests.
56
+ */
57
+ function refreshTemplateCache(options = {}) {
58
+ const { templateRepo = exports.DEFAULT_TEMPLATE_REPO, branch, toolName = exports.DEFAULT_TEMPLATE_TOOL_NAME, cacheBaseDir, cwd, force = false, } = options;
59
+ const template = templateRepo.startsWith('.') ||
60
+ templateRepo.startsWith('/') ||
61
+ templateRepo.startsWith('~')
62
+ ? path_1.default.resolve(cwd ?? process.cwd(), templateRepo)
63
+ : templateRepo;
64
+ if (templateRepo.startsWith('.') ||
65
+ templateRepo.startsWith('/') ||
66
+ templateRepo.startsWith('~')) {
67
+ return {
68
+ repo: template,
69
+ branch,
70
+ refreshed: false,
71
+ offline: false,
72
+ local: true,
73
+ };
74
+ }
75
+ const url = new genomic_1.GitCloner().normalizeUrl(templateRepo);
76
+ const cm = new genomic_1.CacheManager({
77
+ toolName,
78
+ baseDir: resolveCacheBaseDir(cacheBaseDir),
79
+ });
80
+ const key = cm.createKey(url, branch);
81
+ const memoKey = `${key}|${force}`;
82
+ const memoized = templateRefreshMemo.get(memoKey);
83
+ if (memoized)
84
+ return memoized;
85
+ if (process.env.PGPM_TEMPLATE_OFFLINE) {
86
+ const info = {
87
+ repo: url,
88
+ branch,
89
+ refreshed: false,
90
+ offline: true,
91
+ local: false,
92
+ };
93
+ templateRefreshMemo.set(memoKey, info);
94
+ return info;
95
+ }
96
+ let remoteSha;
97
+ try {
98
+ const output = (0, child_process_1.execFileSync)('git', ['ls-remote', '--', url, branch ?? 'HEAD'], { stdio: 'pipe', encoding: 'utf-8', timeout: 15_000 });
99
+ const match = output.match(/\b([0-9a-f]{40})\b/i);
100
+ if (!match)
101
+ throw new Error('git ls-remote returned no commit SHA');
102
+ remoteSha = match[1];
103
+ }
104
+ catch {
105
+ const info = {
106
+ repo: url,
107
+ branch,
108
+ refreshed: false,
109
+ offline: true,
110
+ local: false,
111
+ };
112
+ templateRefreshMemo.set(memoKey, info);
113
+ return info;
114
+ }
115
+ const refPath = path_1.default.join(cm.getMetadataDir(), `${key}.ref.json`);
116
+ let previousSha;
117
+ try {
118
+ const ref = JSON.parse(fs_1.default.readFileSync(refPath, 'utf8'));
119
+ previousSha = typeof ref.sha === 'string' ? ref.sha : undefined;
120
+ }
121
+ catch {
122
+ // A missing or malformed sidecar is treated as unknown.
123
+ }
124
+ const cachePath = path_1.default.join(cm.getReposDir(), key);
125
+ const refreshed = force ||
126
+ previousSha !== remoteSha ||
127
+ (fs_1.default.existsSync(cachePath) && !previousSha);
128
+ if (refreshed)
129
+ cm.clear(key);
130
+ fs_1.default.mkdirSync(cm.getMetadataDir(), { recursive: true });
131
+ fs_1.default.writeFileSync(refPath, JSON.stringify({ sha: remoteSha, checkedAt: Date.now() }, null, 2));
132
+ const info = {
133
+ repo: url,
134
+ branch,
135
+ remoteSha,
136
+ refreshed,
137
+ offline: false,
138
+ local: false,
139
+ };
140
+ templateRefreshMemo.set(memoKey, info);
141
+ return info;
142
+ }
143
+ function formatAge(lastUpdated) {
144
+ const seconds = Math.max(0, Math.floor((Date.now() - lastUpdated) / 1000));
145
+ if (seconds < 60)
146
+ return `${seconds}s`;
147
+ const minutes = Math.floor(seconds / 60);
148
+ if (minutes < 60)
149
+ return `${minutes}m`;
150
+ const hours = Math.floor(minutes / 60);
151
+ if (hours < 24)
152
+ return `${hours}h`;
153
+ return `${Math.floor(hours / 24)}d`;
154
+ }
155
+ function describeTemplateSource(info, opts = {}) {
156
+ if (info.local)
157
+ return `using local template ${info.repo}`;
158
+ const repoDisplay = info.repo
159
+ .replace(/^https:\/\/github\.com\//, '')
160
+ .replace(/\.git$/, '');
161
+ const label = `${repoDisplay}@${info.branch ?? 'HEAD'}`;
162
+ const shortSha = info.remoteSha?.slice(0, 7);
163
+ let fetched;
164
+ if (info.refreshed) {
165
+ fetched = 'fetched just now';
166
+ }
167
+ else if (info.remoteSha) {
168
+ const url = new genomic_1.GitCloner().normalizeUrl(info.repo);
169
+ const cm = new genomic_1.CacheManager({
170
+ toolName: opts.toolName ?? exports.DEFAULT_TEMPLATE_TOOL_NAME,
171
+ baseDir: resolveCacheBaseDir(opts.cacheBaseDir),
172
+ });
173
+ const key = cm.createKey(url, info.branch);
174
+ const metadata = cm.getMetadata(key);
175
+ if (metadata?.lastUpdated)
176
+ fetched = `fetched ${formatAge(metadata.lastUpdated)} ago`;
177
+ }
178
+ const details = [
179
+ shortSha,
180
+ fetched,
181
+ ].filter(Boolean).join(', ');
182
+ let result = `using ${label}${details ? ` (${details})` : ''}`;
183
+ if (info.offline)
184
+ result += ' (offline, using cached copy)';
185
+ return result;
186
+ }
38
187
  function inspectTemplate(options) {
39
188
  const { fromPath, templateRepo = exports.DEFAULT_TEMPLATE_REPO, branch, cacheTtlMs = exports.DEFAULT_TEMPLATE_TTL_MS, toolName = exports.DEFAULT_TEMPLATE_TOOL_NAME, cwd, cacheBaseDir, dir, } = options;
40
189
  const scaffolder = new genomic_1.TemplateScaffolder({
@@ -1,4 +1,6 @@
1
- import { TemplateScaffolder } from 'genomic';
1
+ import { execFileSync } from 'child_process';
2
+ import fs from 'fs';
3
+ import { CacheManager, GitCloner, TemplateScaffolder } from 'genomic';
2
4
  import os from 'os';
3
5
  import path from 'path';
4
6
  export { SkillInstaller } from 'genomic';
@@ -26,6 +28,150 @@ function resolveCacheBaseDir(cacheBaseDir) {
26
28
  }
27
29
  return undefined;
28
30
  }
31
+ const templateRefreshMemo = new Map();
32
+ /**
33
+ * Clear the per-process refresh memo. Primarily useful for tests and callers
34
+ * that begin a new init run in the same process.
35
+ */
36
+ export function _resetTemplateRefreshMemo() {
37
+ templateRefreshMemo.clear();
38
+ }
39
+ /**
40
+ * Check the remote template revision and clear a stale local clone.
41
+ *
42
+ * Set PGPM_TEMPLATE_OFFLINE to skip `git ls-remote`, which is useful for tests
43
+ * and environments that must not make network requests.
44
+ */
45
+ export function refreshTemplateCache(options = {}) {
46
+ const { templateRepo = DEFAULT_TEMPLATE_REPO, branch, toolName = DEFAULT_TEMPLATE_TOOL_NAME, cacheBaseDir, cwd, force = false, } = options;
47
+ const template = templateRepo.startsWith('.') ||
48
+ templateRepo.startsWith('/') ||
49
+ templateRepo.startsWith('~')
50
+ ? path.resolve(cwd ?? process.cwd(), templateRepo)
51
+ : templateRepo;
52
+ if (templateRepo.startsWith('.') ||
53
+ templateRepo.startsWith('/') ||
54
+ templateRepo.startsWith('~')) {
55
+ return {
56
+ repo: template,
57
+ branch,
58
+ refreshed: false,
59
+ offline: false,
60
+ local: true,
61
+ };
62
+ }
63
+ const url = new GitCloner().normalizeUrl(templateRepo);
64
+ const cm = new CacheManager({
65
+ toolName,
66
+ baseDir: resolveCacheBaseDir(cacheBaseDir),
67
+ });
68
+ const key = cm.createKey(url, branch);
69
+ const memoKey = `${key}|${force}`;
70
+ const memoized = templateRefreshMemo.get(memoKey);
71
+ if (memoized)
72
+ return memoized;
73
+ if (process.env.PGPM_TEMPLATE_OFFLINE) {
74
+ const info = {
75
+ repo: url,
76
+ branch,
77
+ refreshed: false,
78
+ offline: true,
79
+ local: false,
80
+ };
81
+ templateRefreshMemo.set(memoKey, info);
82
+ return info;
83
+ }
84
+ let remoteSha;
85
+ try {
86
+ const output = execFileSync('git', ['ls-remote', '--', url, branch ?? 'HEAD'], { stdio: 'pipe', encoding: 'utf-8', timeout: 15_000 });
87
+ const match = output.match(/\b([0-9a-f]{40})\b/i);
88
+ if (!match)
89
+ throw new Error('git ls-remote returned no commit SHA');
90
+ remoteSha = match[1];
91
+ }
92
+ catch {
93
+ const info = {
94
+ repo: url,
95
+ branch,
96
+ refreshed: false,
97
+ offline: true,
98
+ local: false,
99
+ };
100
+ templateRefreshMemo.set(memoKey, info);
101
+ return info;
102
+ }
103
+ const refPath = path.join(cm.getMetadataDir(), `${key}.ref.json`);
104
+ let previousSha;
105
+ try {
106
+ const ref = JSON.parse(fs.readFileSync(refPath, 'utf8'));
107
+ previousSha = typeof ref.sha === 'string' ? ref.sha : undefined;
108
+ }
109
+ catch {
110
+ // A missing or malformed sidecar is treated as unknown.
111
+ }
112
+ const cachePath = path.join(cm.getReposDir(), key);
113
+ const refreshed = force ||
114
+ previousSha !== remoteSha ||
115
+ (fs.existsSync(cachePath) && !previousSha);
116
+ if (refreshed)
117
+ cm.clear(key);
118
+ fs.mkdirSync(cm.getMetadataDir(), { recursive: true });
119
+ fs.writeFileSync(refPath, JSON.stringify({ sha: remoteSha, checkedAt: Date.now() }, null, 2));
120
+ const info = {
121
+ repo: url,
122
+ branch,
123
+ remoteSha,
124
+ refreshed,
125
+ offline: false,
126
+ local: false,
127
+ };
128
+ templateRefreshMemo.set(memoKey, info);
129
+ return info;
130
+ }
131
+ function formatAge(lastUpdated) {
132
+ const seconds = Math.max(0, Math.floor((Date.now() - lastUpdated) / 1000));
133
+ if (seconds < 60)
134
+ return `${seconds}s`;
135
+ const minutes = Math.floor(seconds / 60);
136
+ if (minutes < 60)
137
+ return `${minutes}m`;
138
+ const hours = Math.floor(minutes / 60);
139
+ if (hours < 24)
140
+ return `${hours}h`;
141
+ return `${Math.floor(hours / 24)}d`;
142
+ }
143
+ export function describeTemplateSource(info, opts = {}) {
144
+ if (info.local)
145
+ return `using local template ${info.repo}`;
146
+ const repoDisplay = info.repo
147
+ .replace(/^https:\/\/github\.com\//, '')
148
+ .replace(/\.git$/, '');
149
+ const label = `${repoDisplay}@${info.branch ?? 'HEAD'}`;
150
+ const shortSha = info.remoteSha?.slice(0, 7);
151
+ let fetched;
152
+ if (info.refreshed) {
153
+ fetched = 'fetched just now';
154
+ }
155
+ else if (info.remoteSha) {
156
+ const url = new GitCloner().normalizeUrl(info.repo);
157
+ const cm = new CacheManager({
158
+ toolName: opts.toolName ?? DEFAULT_TEMPLATE_TOOL_NAME,
159
+ baseDir: resolveCacheBaseDir(opts.cacheBaseDir),
160
+ });
161
+ const key = cm.createKey(url, info.branch);
162
+ const metadata = cm.getMetadata(key);
163
+ if (metadata?.lastUpdated)
164
+ fetched = `fetched ${formatAge(metadata.lastUpdated)} ago`;
165
+ }
166
+ const details = [
167
+ shortSha,
168
+ fetched,
169
+ ].filter(Boolean).join(', ');
170
+ let result = `using ${label}${details ? ` (${details})` : ''}`;
171
+ if (info.offline)
172
+ result += ' (offline, using cached copy)';
173
+ return result;
174
+ }
29
175
  export function inspectTemplate(options) {
30
176
  const { fromPath, templateRepo = DEFAULT_TEMPLATE_REPO, branch, cacheTtlMs = DEFAULT_TEMPLATE_TTL_MS, toolName = DEFAULT_TEMPLATE_TOOL_NAME, cwd, cacheBaseDir, dir, } = options;
31
177
  const scaffolder = new TemplateScaffolder({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pgpmjs/core",
3
- "version": "7.29.0",
3
+ "version": "7.31.0",
4
4
  "author": "Constructive <developers@constructive.io>",
5
5
  "description": "PGPM Package and Migration Tools",
6
6
  "main": "index.js",
@@ -51,15 +51,15 @@
51
51
  "dependencies": {
52
52
  "@pgpmjs/ast": "^0.15.0",
53
53
  "@pgpmjs/bundle": "^0.18.0",
54
- "@pgpmjs/diff": "^0.7.0",
54
+ "@pgpmjs/diff": "^0.8.0",
55
55
  "@pgpmjs/env": "^2.45.0",
56
56
  "@pgpmjs/logger": "^2.26.0",
57
57
  "@pgpmjs/server-utils": "^3.29.0",
58
- "@pgpmjs/slice": "^0.18.0",
59
- "@pgpmjs/transform": "^0.27.0",
58
+ "@pgpmjs/slice": "^0.19.0",
59
+ "@pgpmjs/transform": "^0.28.0",
60
60
  "@pgpmjs/types": "^2.55.0",
61
61
  "cross-spawn": "^7.0.6",
62
- "csv-to-pg": "^4.13.0",
62
+ "csv-to-pg": "^4.14.0",
63
63
  "genomic": "^5.6.6",
64
64
  "git-changed": "^0.4.0",
65
65
  "glob": "^13.0.6",
@@ -67,9 +67,9 @@
67
67
  "pg": "^8.21.0",
68
68
  "pg-cache": "^3.29.0",
69
69
  "pg-env": "^1.32.0",
70
- "pgsql-deparser": "^18.3.6",
71
- "pgsql-parser": "^18.2.6",
70
+ "pgsql-deparser": "^18.3.7",
71
+ "pgsql-parser": "^18.2.7",
72
72
  "yanse": "^0.2.2"
73
73
  },
74
- "gitHead": "baa84094b8541eb5000701b0060aa5b298032dc7"
74
+ "gitHead": "75b888926505b473eeef7a76fea1554e5a1cf4cc"
75
75
  }