skedyul 1.0.2 → 1.0.4
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/dist/.build-stamp +1 -1
- package/dist/cli/commands/auth/index.js +43 -23
- package/dist/cli/commands/auth/list.d.ts +1 -0
- package/dist/cli/commands/auth/list.js +55 -0
- package/dist/cli/commands/auth/login.js +38 -21
- package/dist/cli/commands/auth/logout.js +76 -8
- package/dist/cli/commands/auth/status.js +59 -28
- package/dist/cli/commands/auth/use.d.ts +1 -0
- package/dist/cli/commands/auth/use.js +75 -0
- package/dist/cli/commands/invoke-remote.d.ts +1 -0
- package/dist/cli/commands/invoke-remote.js +322 -0
- package/dist/cli/index.js +7 -0
- package/dist/cli/utils/auth.d.ts +40 -4
- package/dist/cli/utils/auth.js +232 -34
- package/dist/config/types/model.d.ts +15 -10
- package/dist/schemas.d.ts +136 -201
- package/dist/schemas.js +2 -4
- package/package.json +1 -1
package/dist/cli/utils/auth.js
CHANGED
|
@@ -33,6 +33,17 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.generateProfileName = generateProfileName;
|
|
37
|
+
exports.ensureUniqueProfileName = ensureUniqueProfileName;
|
|
38
|
+
exports.getProfiles = getProfiles;
|
|
39
|
+
exports.saveProfiles = saveProfiles;
|
|
40
|
+
exports.getProfile = getProfile;
|
|
41
|
+
exports.saveProfile = saveProfile;
|
|
42
|
+
exports.deleteProfile = deleteProfile;
|
|
43
|
+
exports.listProfiles = listProfiles;
|
|
44
|
+
exports.clearAllProfiles = clearAllProfiles;
|
|
45
|
+
exports.getActiveProfileName = getActiveProfileName;
|
|
46
|
+
exports.setActiveProfile = setActiveProfile;
|
|
36
47
|
exports.getCredentials = getCredentials;
|
|
37
48
|
exports.saveCredentials = saveCredentials;
|
|
38
49
|
exports.clearCredentials = clearCredentials;
|
|
@@ -52,9 +63,9 @@ const http = __importStar(require("http"));
|
|
|
52
63
|
// Paths
|
|
53
64
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
54
65
|
const SKEDYUL_HOME_DIR = path.join(os.homedir(), '.skedyul');
|
|
55
|
-
const
|
|
66
|
+
const PROFILES_FILE = path.join(SKEDYUL_HOME_DIR, 'profiles.json');
|
|
56
67
|
const CONFIG_FILE = path.join(SKEDYUL_HOME_DIR, 'config.json');
|
|
57
|
-
|
|
68
|
+
const LEGACY_CREDENTIALS_FILE = path.join(SKEDYUL_HOME_DIR, 'credentials.json');
|
|
58
69
|
const LOCAL_CONFIG_FILE = '.skedyul.local.json';
|
|
59
70
|
const DEFAULT_SERVER_URL = 'https://admin.skedyul.it';
|
|
60
71
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -66,37 +77,234 @@ function ensureHomeDir() {
|
|
|
66
77
|
}
|
|
67
78
|
}
|
|
68
79
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
69
|
-
//
|
|
80
|
+
// Profile Name Generation
|
|
70
81
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
82
|
+
/**
|
|
83
|
+
* Generate a sensible profile name from a server URL.
|
|
84
|
+
*/
|
|
85
|
+
function generateProfileName(serverUrl) {
|
|
75
86
|
try {
|
|
76
|
-
const
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
87
|
+
const url = new URL(serverUrl);
|
|
88
|
+
const hostname = url.hostname.toLowerCase();
|
|
89
|
+
if (hostname === 'localhost' || hostname === '127.0.0.1') {
|
|
90
|
+
return 'local';
|
|
91
|
+
}
|
|
92
|
+
if (hostname.includes('staging')) {
|
|
93
|
+
return 'staging';
|
|
94
|
+
}
|
|
95
|
+
if (hostname === 'admin.skedyul.it' || hostname === 'app.skedyul.com') {
|
|
96
|
+
return 'production';
|
|
84
97
|
}
|
|
85
|
-
|
|
98
|
+
const parts = hostname.split('.');
|
|
99
|
+
if (parts.length > 0) {
|
|
100
|
+
return parts[0].replace(/[^a-z0-9-]/g, '');
|
|
101
|
+
}
|
|
102
|
+
return 'default';
|
|
86
103
|
}
|
|
87
104
|
catch {
|
|
88
|
-
return
|
|
105
|
+
return 'default';
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Ensure a profile name is unique by appending a number if needed.
|
|
110
|
+
*/
|
|
111
|
+
function ensureUniqueProfileName(baseName, existingProfiles) {
|
|
112
|
+
if (!existingProfiles[baseName]) {
|
|
113
|
+
return baseName;
|
|
114
|
+
}
|
|
115
|
+
let counter = 2;
|
|
116
|
+
while (existingProfiles[`${baseName}-${counter}`]) {
|
|
117
|
+
counter++;
|
|
118
|
+
}
|
|
119
|
+
return `${baseName}-${counter}`;
|
|
120
|
+
}
|
|
121
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
122
|
+
// Migration from Legacy credentials.json
|
|
123
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
124
|
+
function migrateLegacyCredentials() {
|
|
125
|
+
if (!fs.existsSync(LEGACY_CREDENTIALS_FILE)) {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
if (fs.existsSync(PROFILES_FILE)) {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
try {
|
|
132
|
+
const content = fs.readFileSync(LEGACY_CREDENTIALS_FILE, 'utf-8');
|
|
133
|
+
const legacy = JSON.parse(content);
|
|
134
|
+
const profileName = generateProfileName(legacy.serverUrl);
|
|
135
|
+
const profile = {
|
|
136
|
+
serverUrl: legacy.serverUrl,
|
|
137
|
+
token: legacy.token,
|
|
138
|
+
userId: legacy.userId,
|
|
139
|
+
username: legacy.username,
|
|
140
|
+
email: legacy.email,
|
|
141
|
+
expiresAt: legacy.expiresAt,
|
|
142
|
+
createdAt: legacy.createdAt,
|
|
143
|
+
};
|
|
144
|
+
const profilesFile = {
|
|
145
|
+
profiles: {
|
|
146
|
+
[profileName]: profile,
|
|
147
|
+
},
|
|
148
|
+
};
|
|
149
|
+
ensureHomeDir();
|
|
150
|
+
fs.writeFileSync(PROFILES_FILE, JSON.stringify(profilesFile, null, 2), {
|
|
151
|
+
mode: 0o600,
|
|
152
|
+
});
|
|
153
|
+
const config = getConfig();
|
|
154
|
+
config.activeProfile = profileName;
|
|
155
|
+
saveConfig(config);
|
|
156
|
+
fs.unlinkSync(LEGACY_CREDENTIALS_FILE);
|
|
157
|
+
console.error(`Migrated credentials to profile: ${profileName}`);
|
|
158
|
+
}
|
|
159
|
+
catch (error) {
|
|
160
|
+
console.error('Failed to migrate legacy credentials:', error);
|
|
89
161
|
}
|
|
90
162
|
}
|
|
91
|
-
|
|
163
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
164
|
+
// Profiles Management
|
|
165
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
166
|
+
function getProfiles() {
|
|
167
|
+
migrateLegacyCredentials();
|
|
168
|
+
if (!fs.existsSync(PROFILES_FILE)) {
|
|
169
|
+
return { profiles: {} };
|
|
170
|
+
}
|
|
171
|
+
try {
|
|
172
|
+
const content = fs.readFileSync(PROFILES_FILE, 'utf-8');
|
|
173
|
+
return JSON.parse(content);
|
|
174
|
+
}
|
|
175
|
+
catch {
|
|
176
|
+
return { profiles: {} };
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
function saveProfiles(profilesFile) {
|
|
92
180
|
ensureHomeDir();
|
|
93
|
-
fs.writeFileSync(
|
|
94
|
-
mode: 0o600,
|
|
181
|
+
fs.writeFileSync(PROFILES_FILE, JSON.stringify(profilesFile, null, 2), {
|
|
182
|
+
mode: 0o600,
|
|
95
183
|
});
|
|
96
184
|
}
|
|
185
|
+
function getProfile(name) {
|
|
186
|
+
const profilesFile = getProfiles();
|
|
187
|
+
const profile = profilesFile.profiles[name];
|
|
188
|
+
if (!profile) {
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
if (profile.expiresAt) {
|
|
192
|
+
const expiresAt = new Date(profile.expiresAt);
|
|
193
|
+
if (expiresAt < new Date()) {
|
|
194
|
+
return null;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return profile;
|
|
198
|
+
}
|
|
199
|
+
function saveProfile(name, profile) {
|
|
200
|
+
const profilesFile = getProfiles();
|
|
201
|
+
profilesFile.profiles[name] = profile;
|
|
202
|
+
saveProfiles(profilesFile);
|
|
203
|
+
}
|
|
204
|
+
function deleteProfile(name) {
|
|
205
|
+
const profilesFile = getProfiles();
|
|
206
|
+
if (!profilesFile.profiles[name]) {
|
|
207
|
+
return false;
|
|
208
|
+
}
|
|
209
|
+
delete profilesFile.profiles[name];
|
|
210
|
+
saveProfiles(profilesFile);
|
|
211
|
+
const config = getConfig();
|
|
212
|
+
if (config.activeProfile === name) {
|
|
213
|
+
const remainingProfiles = Object.keys(profilesFile.profiles);
|
|
214
|
+
config.activeProfile = remainingProfiles[0] ?? undefined;
|
|
215
|
+
saveConfig(config);
|
|
216
|
+
}
|
|
217
|
+
return true;
|
|
218
|
+
}
|
|
219
|
+
function listProfiles() {
|
|
220
|
+
const profilesFile = getProfiles();
|
|
221
|
+
const config = getConfig();
|
|
222
|
+
const activeProfile = config.activeProfile;
|
|
223
|
+
return Object.entries(profilesFile.profiles).map(([name, profile]) => {
|
|
224
|
+
let isExpired = false;
|
|
225
|
+
if (profile.expiresAt) {
|
|
226
|
+
isExpired = new Date(profile.expiresAt) < new Date();
|
|
227
|
+
}
|
|
228
|
+
return {
|
|
229
|
+
name,
|
|
230
|
+
profile,
|
|
231
|
+
isActive: name === activeProfile,
|
|
232
|
+
isExpired,
|
|
233
|
+
};
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
function clearAllProfiles() {
|
|
237
|
+
if (fs.existsSync(PROFILES_FILE)) {
|
|
238
|
+
fs.unlinkSync(PROFILES_FILE);
|
|
239
|
+
}
|
|
240
|
+
const config = getConfig();
|
|
241
|
+
delete config.activeProfile;
|
|
242
|
+
saveConfig(config);
|
|
243
|
+
}
|
|
244
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
245
|
+
// Active Profile Management
|
|
246
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
247
|
+
function getActiveProfileName() {
|
|
248
|
+
const config = getConfig();
|
|
249
|
+
return config.activeProfile ?? null;
|
|
250
|
+
}
|
|
251
|
+
function setActiveProfile(name) {
|
|
252
|
+
const profilesFile = getProfiles();
|
|
253
|
+
if (!profilesFile.profiles[name]) {
|
|
254
|
+
return false;
|
|
255
|
+
}
|
|
256
|
+
const config = getConfig();
|
|
257
|
+
config.activeProfile = name;
|
|
258
|
+
saveConfig(config);
|
|
259
|
+
return true;
|
|
260
|
+
}
|
|
261
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
262
|
+
// Credentials Management (uses active profile)
|
|
263
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
264
|
+
function getCredentials() {
|
|
265
|
+
const activeProfileName = getActiveProfileName();
|
|
266
|
+
if (!activeProfileName) {
|
|
267
|
+
return null;
|
|
268
|
+
}
|
|
269
|
+
const profile = getProfile(activeProfileName);
|
|
270
|
+
if (!profile) {
|
|
271
|
+
return null;
|
|
272
|
+
}
|
|
273
|
+
return {
|
|
274
|
+
token: profile.token,
|
|
275
|
+
userId: profile.userId,
|
|
276
|
+
username: profile.username,
|
|
277
|
+
email: profile.email,
|
|
278
|
+
serverUrl: profile.serverUrl,
|
|
279
|
+
expiresAt: profile.expiresAt,
|
|
280
|
+
createdAt: profile.createdAt,
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
function saveCredentials(credentials, profileName) {
|
|
284
|
+
const name = profileName ?? generateProfileName(credentials.serverUrl);
|
|
285
|
+
const profilesFile = getProfiles();
|
|
286
|
+
const finalName = profileName
|
|
287
|
+
? name
|
|
288
|
+
: ensureUniqueProfileName(name, profilesFile.profiles);
|
|
289
|
+
const profile = {
|
|
290
|
+
serverUrl: credentials.serverUrl,
|
|
291
|
+
token: credentials.token,
|
|
292
|
+
userId: credentials.userId,
|
|
293
|
+
username: credentials.username,
|
|
294
|
+
email: credentials.email,
|
|
295
|
+
expiresAt: credentials.expiresAt,
|
|
296
|
+
createdAt: credentials.createdAt,
|
|
297
|
+
};
|
|
298
|
+
saveProfile(finalName, profile);
|
|
299
|
+
const config = getConfig();
|
|
300
|
+
config.activeProfile = finalName;
|
|
301
|
+
saveConfig(config);
|
|
302
|
+
return finalName;
|
|
303
|
+
}
|
|
97
304
|
function clearCredentials() {
|
|
98
|
-
|
|
99
|
-
|
|
305
|
+
const activeProfileName = getActiveProfileName();
|
|
306
|
+
if (activeProfileName) {
|
|
307
|
+
deleteProfile(activeProfileName);
|
|
100
308
|
}
|
|
101
309
|
}
|
|
102
310
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -137,21 +345,17 @@ function getLocalConfig() {
|
|
|
137
345
|
}
|
|
138
346
|
/**
|
|
139
347
|
* Get the server URL to use.
|
|
140
|
-
* Priority: CLI flag > local config >
|
|
348
|
+
* Priority: CLI flag > local config > active profile > global config > default
|
|
141
349
|
*/
|
|
142
350
|
function getServerUrl(override) {
|
|
143
|
-
// 1. CLI flag takes precedence
|
|
144
351
|
if (override)
|
|
145
352
|
return override;
|
|
146
|
-
// 2. Local project config (for development)
|
|
147
353
|
const localConfig = getLocalConfig();
|
|
148
354
|
if (localConfig.serverUrl)
|
|
149
355
|
return localConfig.serverUrl;
|
|
150
|
-
// 3. Stored credentials
|
|
151
356
|
const credentials = getCredentials();
|
|
152
357
|
if (credentials?.serverUrl)
|
|
153
358
|
return credentials.serverUrl;
|
|
154
|
-
// 4. Global config
|
|
155
359
|
return getConfig().defaultServer;
|
|
156
360
|
}
|
|
157
361
|
/**
|
|
@@ -183,7 +387,6 @@ async function startOAuthCallback(serverUrl) {
|
|
|
183
387
|
const server = http.createServer((req, res) => {
|
|
184
388
|
const url = new URL(req.url ?? '/', `http://localhost`);
|
|
185
389
|
const searchParams = url.searchParams;
|
|
186
|
-
// Handle callback
|
|
187
390
|
if (url.pathname === '/callback') {
|
|
188
391
|
const token = searchParams.get('token');
|
|
189
392
|
const userId = searchParams.get('userId');
|
|
@@ -227,7 +430,7 @@ async function startOAuthCallback(serverUrl) {
|
|
|
227
430
|
res.end(`
|
|
228
431
|
<html>
|
|
229
432
|
<body style="font-family: system-ui; padding: 40px; text-align: center;">
|
|
230
|
-
<h1 style="color: #38a169;"
|
|
433
|
+
<h1 style="color: #38a169;">Authentication Successful</h1>
|
|
231
434
|
<p>Logged in as <strong>${email}</strong></p>
|
|
232
435
|
<p>You can close this window and return to the terminal.</p>
|
|
233
436
|
</body>
|
|
@@ -248,7 +451,6 @@ async function startOAuthCallback(serverUrl) {
|
|
|
248
451
|
res.end('Not found');
|
|
249
452
|
}
|
|
250
453
|
});
|
|
251
|
-
// Find an available port
|
|
252
454
|
server.listen(0, '127.0.0.1', () => {
|
|
253
455
|
const address = server.address();
|
|
254
456
|
if (!address || typeof address === 'string') {
|
|
@@ -262,7 +464,6 @@ async function startOAuthCallback(serverUrl) {
|
|
|
262
464
|
console.log(`Opening browser for authentication...`);
|
|
263
465
|
console.log(`If browser doesn't open, visit: ${authUrl}`);
|
|
264
466
|
console.log(`Waiting for callback on ${callbackUrl}...`);
|
|
265
|
-
// Try to open browser
|
|
266
467
|
openBrowser(authUrl).catch(() => {
|
|
267
468
|
console.log(`(Could not open browser automatically)`);
|
|
268
469
|
});
|
|
@@ -271,7 +472,6 @@ async function startOAuthCallback(serverUrl) {
|
|
|
271
472
|
cleanup();
|
|
272
473
|
reject(err);
|
|
273
474
|
});
|
|
274
|
-
// Timeout after 5 minutes
|
|
275
475
|
timeoutId = setTimeout(() => {
|
|
276
476
|
server.close();
|
|
277
477
|
reject(new Error('Authentication timed out'));
|
|
@@ -282,13 +482,11 @@ async function startOAuthCallback(serverUrl) {
|
|
|
282
482
|
* Open a URL in the default browser
|
|
283
483
|
*/
|
|
284
484
|
async function openBrowser(url) {
|
|
285
|
-
// Try to dynamically import 'open' package
|
|
286
485
|
try {
|
|
287
486
|
const open = await Promise.resolve().then(() => __importStar(require('open')));
|
|
288
487
|
await open.default(url);
|
|
289
488
|
}
|
|
290
489
|
catch {
|
|
291
|
-
// Fallback to platform-specific commands
|
|
292
490
|
const { exec } = await Promise.resolve().then(() => __importStar(require('child_process')));
|
|
293
491
|
const platform = process.platform;
|
|
294
492
|
let command;
|
|
@@ -23,10 +23,6 @@ import type { ResourceDependency } from './resource';
|
|
|
23
23
|
* - 'object': JSON object
|
|
24
24
|
*/
|
|
25
25
|
export type FieldType = 'string' | 'long_string' | 'text' | 'number' | 'boolean' | 'date' | 'datetime' | 'time' | 'file' | 'image' | 'relation' | 'object';
|
|
26
|
-
/**
|
|
27
|
-
* Relationship cardinality between models.
|
|
28
|
-
*/
|
|
29
|
-
export type Cardinality = 'one_to_one' | 'one_to_many' | 'many_to_one' | 'many_to_many';
|
|
30
26
|
/**
|
|
31
27
|
* Behavior when a related record is deleted.
|
|
32
28
|
* - 'none': No action (orphan the reference)
|
|
@@ -122,17 +118,26 @@ export interface RelationshipLink {
|
|
|
122
118
|
field: string;
|
|
123
119
|
/** Display label for this side of the relationship */
|
|
124
120
|
label: string;
|
|
125
|
-
/** Cardinality from this side's perspective */
|
|
126
|
-
cardinality: Cardinality;
|
|
127
|
-
/** Behavior when this record is deleted */
|
|
128
|
-
onDelete?: OnDelete;
|
|
129
121
|
}
|
|
122
|
+
/**
|
|
123
|
+
* Relationship cardinality from source (one) to target (many or one).
|
|
124
|
+
* - 'one_to_one': One source record relates to one target record
|
|
125
|
+
* - 'one_to_many': One source record relates to many target records
|
|
126
|
+
*
|
|
127
|
+
* Note: For many-to-one relationships, swap source and target and use 'one_to_many'.
|
|
128
|
+
*/
|
|
129
|
+
export type Cardinality = 'one_to_one' | 'one_to_many';
|
|
130
130
|
/**
|
|
131
131
|
* Relationship definition between two models.
|
|
132
|
+
* Source is always the "one" side, target is the "many" side (for one_to_many).
|
|
132
133
|
*/
|
|
133
134
|
export interface RelationshipDefinition {
|
|
134
|
-
/** Source side of the relationship */
|
|
135
|
+
/** Source side of the relationship (the "one" side) */
|
|
135
136
|
source: RelationshipLink;
|
|
136
|
-
/** Target side of the relationship */
|
|
137
|
+
/** Target side of the relationship (the "many" side for one_to_many) */
|
|
137
138
|
target: RelationshipLink;
|
|
139
|
+
/** Cardinality: 'one_to_one' or 'one_to_many' */
|
|
140
|
+
cardinality: Cardinality;
|
|
141
|
+
/** Behavior when a related record is deleted */
|
|
142
|
+
onDelete?: OnDelete;
|
|
138
143
|
}
|