@uns-kit/cli 0.0.12 → 0.0.14
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/index.js +466 -6
- package/package.json +2 -2
- package/templates/codegen/codegen.ts +15 -0
- package/templates/codegen/src/uns/uns-tags.ts +1 -0
- package/templates/codegen/src/uns/uns-topics.ts +1 -0
- package/templates/default/config.json +3 -1
- package/templates/default/src/uns/uns-tags.ts +2 -0
- package/templates/default/src/uns/uns-topics.ts +2 -0
- package/templates/vscode/.vscode/launch.json +22 -0
- package/templates/vscode/uns-kit.code-workspace +13 -0
- package/templates/default/uns-library.json +0 -4
package/dist/index.js
CHANGED
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { execFile } from "node:child_process";
|
|
3
|
+
import { access, cp, mkdir, readFile, readdir, stat, writeFile, copyFile } from "node:fs/promises";
|
|
3
4
|
import path from "node:path";
|
|
4
5
|
import { fileURLToPath } from "node:url";
|
|
5
6
|
import { createRequire } from "node:module";
|
|
6
7
|
import process from "node:process";
|
|
7
8
|
import readline from "node:readline/promises";
|
|
9
|
+
import { promisify } from "node:util";
|
|
10
|
+
import * as azdev from "azure-devops-node-api";
|
|
8
11
|
const __filename = fileURLToPath(import.meta.url);
|
|
9
12
|
const __dirname = path.dirname(__filename);
|
|
10
13
|
const require = createRequire(import.meta.url);
|
|
11
14
|
const coreVersion = resolveCoreVersion();
|
|
15
|
+
const execFileAsync = promisify(execFile);
|
|
16
|
+
const AZURE_DEVOPS_PROVIDER = "azure-devops";
|
|
12
17
|
async function main() {
|
|
13
18
|
const args = process.argv.slice(2);
|
|
14
19
|
const command = args[0];
|
|
@@ -27,6 +32,28 @@ async function main() {
|
|
|
27
32
|
}
|
|
28
33
|
return;
|
|
29
34
|
}
|
|
35
|
+
if (command === "configure-vscode") {
|
|
36
|
+
const targetPath = args[1];
|
|
37
|
+
try {
|
|
38
|
+
await configureVscode(targetPath);
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
console.error(error.message);
|
|
42
|
+
process.exitCode = 1;
|
|
43
|
+
}
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
if (command === "configure-codegen") {
|
|
47
|
+
const targetPath = args[1];
|
|
48
|
+
try {
|
|
49
|
+
await configureCodegen(targetPath);
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
console.error(error.message);
|
|
53
|
+
process.exitCode = 1;
|
|
54
|
+
}
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
30
57
|
if (command === "create") {
|
|
31
58
|
const projectName = args[1];
|
|
32
59
|
if (!projectName) {
|
|
@@ -48,7 +75,13 @@ async function main() {
|
|
|
48
75
|
process.exitCode = 1;
|
|
49
76
|
}
|
|
50
77
|
function printHelp() {
|
|
51
|
-
console.log(
|
|
78
|
+
console.log("\nUsage: uns-kit <command> [options]\n" +
|
|
79
|
+
"\nCommands:\n" +
|
|
80
|
+
" create <name> Scaffold a new UNS application\n" +
|
|
81
|
+
" configure-devops [dir] Configure Azure DevOps tooling in an existing project\n" +
|
|
82
|
+
" configure-vscode [dir] Add VS Code workspace configuration files\n" +
|
|
83
|
+
" configure-codegen [dir] Copy GraphQL codegen template and dependencies\n" +
|
|
84
|
+
" help Show this message\n");
|
|
52
85
|
}
|
|
53
86
|
async function createProject(projectName) {
|
|
54
87
|
const targetDir = path.resolve(process.cwd(), projectName);
|
|
@@ -91,13 +124,47 @@ async function configureDevops(targetPath) {
|
|
|
91
124
|
}
|
|
92
125
|
const pkg = JSON.parse(pkgRaw);
|
|
93
126
|
const config = JSON.parse(configRaw);
|
|
94
|
-
|
|
95
|
-
const
|
|
96
|
-
const
|
|
127
|
+
await ensureGitRepository(targetDir);
|
|
128
|
+
const remoteUrl = await getGitRemoteUrl(targetDir, "origin");
|
|
129
|
+
const remoteInfo = remoteUrl ? parseAzureRemote(remoteUrl) : undefined;
|
|
130
|
+
const repositoryName = inferRepositoryNameFromPackage(pkg.name) || inferRepositoryNameFromPackage(path.basename(targetDir));
|
|
131
|
+
const defaultOrganization = config.devops?.organization?.trim() || remoteInfo?.organization || "sijit";
|
|
132
|
+
const organization = await promptWithDefault(defaultOrganization ? `Azure DevOps organization [${defaultOrganization}]: ` : "Azure DevOps organization: ", defaultOrganization, "Azure DevOps organization is required.");
|
|
133
|
+
const defaultProject = config.devops?.project?.trim() || remoteInfo?.project || "";
|
|
134
|
+
const project = await promptWithDefault(defaultProject ? `Azure DevOps project [${defaultProject}]: ` : "Azure DevOps project: ", defaultProject, "Azure DevOps project is required.");
|
|
97
135
|
if (!config.devops || typeof config.devops !== "object") {
|
|
98
136
|
config.devops = {};
|
|
99
137
|
}
|
|
100
|
-
config.devops
|
|
138
|
+
const devopsConfig = config.devops;
|
|
139
|
+
devopsConfig.provider = AZURE_DEVOPS_PROVIDER;
|
|
140
|
+
devopsConfig.organization = organization;
|
|
141
|
+
devopsConfig.project = project;
|
|
142
|
+
let gitRemoteMessage;
|
|
143
|
+
let repositoryUrlMessage;
|
|
144
|
+
let ensuredRemoteUrl = remoteUrl ?? "";
|
|
145
|
+
if (!remoteUrl) {
|
|
146
|
+
const { gitApi } = await resolveAzureGitApi(organization);
|
|
147
|
+
const repositoryDetails = await ensureAzureRepositoryExists(gitApi, {
|
|
148
|
+
organization,
|
|
149
|
+
project,
|
|
150
|
+
repository: repositoryName,
|
|
151
|
+
});
|
|
152
|
+
ensuredRemoteUrl = repositoryDetails.remoteUrl ?? buildAzureGitRemoteUrl(organization, project, repositoryName);
|
|
153
|
+
await addGitRemote(targetDir, "origin", ensuredRemoteUrl);
|
|
154
|
+
gitRemoteMessage = ` Added git remote origin -> ${ensuredRemoteUrl}`;
|
|
155
|
+
const friendlyUrl = repositoryDetails.webUrl ?? buildAzureRepositoryUrl(organization, project, repositoryName);
|
|
156
|
+
if (friendlyUrl) {
|
|
157
|
+
repositoryUrlMessage = ` Repository URL: ${friendlyUrl}`;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
ensuredRemoteUrl = remoteUrl;
|
|
162
|
+
gitRemoteMessage = ` Git remote origin detected -> ${ensuredRemoteUrl}`;
|
|
163
|
+
const friendlyUrl = buildAzureRepositoryUrl(remoteInfo?.organization ?? organization, remoteInfo?.project ?? project, remoteInfo?.repository ?? repositoryName);
|
|
164
|
+
if (friendlyUrl) {
|
|
165
|
+
repositoryUrlMessage = ` Repository URL: ${friendlyUrl}`;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
101
168
|
const requiredDevDeps = {
|
|
102
169
|
"azure-devops-node-api": "^15.1.0",
|
|
103
170
|
"simple-git": "^3.27.0",
|
|
@@ -123,7 +190,15 @@ async function configureDevops(targetPath) {
|
|
|
123
190
|
await writeFile(packagePath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
|
|
124
191
|
}
|
|
125
192
|
console.log(`\nDevOps tooling configured.`);
|
|
193
|
+
console.log(` DevOps provider: ${AZURE_DEVOPS_PROVIDER}`);
|
|
126
194
|
console.log(` Azure organization: ${organization}`);
|
|
195
|
+
console.log(` Azure project: ${project}`);
|
|
196
|
+
if (repositoryUrlMessage) {
|
|
197
|
+
console.log(repositoryUrlMessage);
|
|
198
|
+
}
|
|
199
|
+
if (gitRemoteMessage) {
|
|
200
|
+
console.log(gitRemoteMessage);
|
|
201
|
+
}
|
|
127
202
|
if (pkgChanged) {
|
|
128
203
|
console.log(" Updated package.json scripts/devDependencies. Run pnpm install to fetch new packages.");
|
|
129
204
|
}
|
|
@@ -131,6 +206,379 @@ async function configureDevops(targetPath) {
|
|
|
131
206
|
console.log(" Existing package.json already contained required entries.");
|
|
132
207
|
}
|
|
133
208
|
}
|
|
209
|
+
async function configureVscode(targetPath) {
|
|
210
|
+
const targetDir = path.resolve(process.cwd(), targetPath ?? ".");
|
|
211
|
+
const templateDir = path.resolve(__dirname, "../templates/vscode");
|
|
212
|
+
try {
|
|
213
|
+
await access(templateDir);
|
|
214
|
+
}
|
|
215
|
+
catch (error) {
|
|
216
|
+
throw new Error("VS Code template directory is missing. Please ensure templates/vscode is available.");
|
|
217
|
+
}
|
|
218
|
+
const { copied, skipped } = await copyTemplateDirectory(templateDir, targetDir, targetDir);
|
|
219
|
+
console.log("\nVS Code configuration files processed.");
|
|
220
|
+
if (copied.length) {
|
|
221
|
+
console.log(" Added:");
|
|
222
|
+
for (const file of copied) {
|
|
223
|
+
console.log(` ${file}`);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
if (skipped.length) {
|
|
227
|
+
console.log(" Skipped (already exists):");
|
|
228
|
+
for (const file of skipped) {
|
|
229
|
+
console.log(` ${file}`);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
if (!copied.length && !skipped.length) {
|
|
233
|
+
console.log(" No files were found in the VS Code template directory.");
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
async function configureCodegen(targetPath) {
|
|
237
|
+
const targetDir = path.resolve(process.cwd(), targetPath ?? ".");
|
|
238
|
+
const templateDir = path.resolve(__dirname, "../templates/codegen");
|
|
239
|
+
const packagePath = path.join(targetDir, "package.json");
|
|
240
|
+
try {
|
|
241
|
+
await access(templateDir);
|
|
242
|
+
}
|
|
243
|
+
catch (error) {
|
|
244
|
+
throw new Error("GraphQL codegen template directory is missing. Please ensure templates/codegen is available.");
|
|
245
|
+
}
|
|
246
|
+
let pkgRaw;
|
|
247
|
+
try {
|
|
248
|
+
pkgRaw = await readFile(packagePath, "utf8");
|
|
249
|
+
}
|
|
250
|
+
catch (error) {
|
|
251
|
+
if (error.code === "ENOENT") {
|
|
252
|
+
throw new Error(`Could not find package.json in ${targetDir}`);
|
|
253
|
+
}
|
|
254
|
+
throw error;
|
|
255
|
+
}
|
|
256
|
+
const pkg = JSON.parse(pkgRaw);
|
|
257
|
+
const { copied, skipped } = await copyTemplateDirectory(templateDir, targetDir, targetDir);
|
|
258
|
+
const devDeps = (pkg.devDependencies ??= {});
|
|
259
|
+
const deps = pkg.dependencies ?? {};
|
|
260
|
+
const requiredDevDeps = {
|
|
261
|
+
"@graphql-codegen/cli": "^5.0.7",
|
|
262
|
+
"@graphql-codegen/typescript": "^4.1.6",
|
|
263
|
+
"@graphql-codegen/typescript-operations": "^4.6.1",
|
|
264
|
+
"@graphql-codegen/typescript-resolvers": "^4.3.1",
|
|
265
|
+
"graphql": "^16.11.0",
|
|
266
|
+
"graphql-request": "^7.2.0"
|
|
267
|
+
};
|
|
268
|
+
let pkgChanged = false;
|
|
269
|
+
for (const [name, version] of Object.entries(requiredDevDeps)) {
|
|
270
|
+
if (!devDeps[name] && !deps[name]) {
|
|
271
|
+
devDeps[name] = version;
|
|
272
|
+
pkgChanged = true;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
const scripts = (pkg.scripts ??= {});
|
|
276
|
+
if (!scripts.codegen) {
|
|
277
|
+
scripts.codegen = "graphql-code-generator --config codegen.ts";
|
|
278
|
+
pkgChanged = true;
|
|
279
|
+
}
|
|
280
|
+
if (!scripts["refresh-uns"]) {
|
|
281
|
+
scripts["refresh-uns"] = "node ./node_modules/@uns-kit/core/dist/tools/refresh-uns.js";
|
|
282
|
+
pkgChanged = true;
|
|
283
|
+
}
|
|
284
|
+
if (pkgChanged) {
|
|
285
|
+
await writeFile(packagePath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
|
|
286
|
+
}
|
|
287
|
+
console.log("\nGraphQL code generation setup complete.");
|
|
288
|
+
if (copied.length) {
|
|
289
|
+
console.log(" Added files:");
|
|
290
|
+
for (const file of copied) {
|
|
291
|
+
console.log(` ${file}`);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
if (skipped.length) {
|
|
295
|
+
console.log(" Skipped existing files:");
|
|
296
|
+
for (const file of skipped) {
|
|
297
|
+
console.log(` ${file}`);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
if (!copied.length && !skipped.length) {
|
|
301
|
+
console.log(" No template files were copied.");
|
|
302
|
+
}
|
|
303
|
+
if (pkgChanged) {
|
|
304
|
+
console.log(" Updated package.json scripts/devDependencies. Run pnpm install to fetch new packages.");
|
|
305
|
+
}
|
|
306
|
+
else {
|
|
307
|
+
console.log(" Existing package.json already contained required scripts and dependencies.");
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
async function ensureGitRepository(dir) {
|
|
311
|
+
try {
|
|
312
|
+
const { stdout } = await execFileAsync("git", ["rev-parse", "--is-inside-work-tree"], {
|
|
313
|
+
cwd: dir,
|
|
314
|
+
encoding: "utf8",
|
|
315
|
+
});
|
|
316
|
+
if (stdout.trim() !== "true") {
|
|
317
|
+
throw new Error(`Directory ${dir} is not a git repository.`);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
catch (error) {
|
|
321
|
+
if (isGitCommandNotFoundError(error)) {
|
|
322
|
+
throw new Error("Git is required to run configure-devops but was not found in PATH.");
|
|
323
|
+
}
|
|
324
|
+
const execError = error;
|
|
325
|
+
const stderr = typeof execError.stderr === "string" ? execError.stderr : "";
|
|
326
|
+
if (stderr.includes("not a git repository")) {
|
|
327
|
+
throw new Error(`Directory ${dir} is not a git repository.`);
|
|
328
|
+
}
|
|
329
|
+
throw error;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
async function getGitRemoteUrl(dir, remoteName) {
|
|
333
|
+
try {
|
|
334
|
+
const { stdout } = await execFileAsync("git", ["remote", "get-url", remoteName], {
|
|
335
|
+
cwd: dir,
|
|
336
|
+
encoding: "utf8",
|
|
337
|
+
});
|
|
338
|
+
return stdout.trim();
|
|
339
|
+
}
|
|
340
|
+
catch (error) {
|
|
341
|
+
if (isGitCommandNotFoundError(error)) {
|
|
342
|
+
throw new Error("Git is required to run configure-devops but was not found in PATH.");
|
|
343
|
+
}
|
|
344
|
+
if (isGitRemoteMissingError(error)) {
|
|
345
|
+
return null;
|
|
346
|
+
}
|
|
347
|
+
throw error;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
async function addGitRemote(dir, remoteName, remoteUrl) {
|
|
351
|
+
try {
|
|
352
|
+
await execFileAsync("git", ["remote", "add", remoteName, remoteUrl], {
|
|
353
|
+
cwd: dir,
|
|
354
|
+
encoding: "utf8",
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
catch (error) {
|
|
358
|
+
if (isGitCommandNotFoundError(error)) {
|
|
359
|
+
throw new Error("Git is required to run configure-devops but was not found in PATH.");
|
|
360
|
+
}
|
|
361
|
+
const execError = error;
|
|
362
|
+
const stderr = typeof execError.stderr === "string" ? execError.stderr.trim() : "";
|
|
363
|
+
if (stderr) {
|
|
364
|
+
throw new Error(`Failed to add git remote origin: ${stderr}`);
|
|
365
|
+
}
|
|
366
|
+
throw new Error(`Failed to add git remote origin: ${error.message}`);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
async function copyTemplateDirectory(sourceDir, targetDir, targetRoot) {
|
|
370
|
+
const entries = await readdir(sourceDir, { withFileTypes: true });
|
|
371
|
+
const copied = [];
|
|
372
|
+
const skipped = [];
|
|
373
|
+
for (const entry of entries) {
|
|
374
|
+
const sourcePath = path.join(sourceDir, entry.name);
|
|
375
|
+
const destinationPath = path.join(targetDir, entry.name);
|
|
376
|
+
const relativePath = path.relative(targetRoot, destinationPath) || entry.name;
|
|
377
|
+
if (entry.isDirectory()) {
|
|
378
|
+
await mkdir(destinationPath, { recursive: true });
|
|
379
|
+
const result = await copyTemplateDirectory(sourcePath, destinationPath, targetRoot);
|
|
380
|
+
copied.push(...result.copied);
|
|
381
|
+
skipped.push(...result.skipped);
|
|
382
|
+
continue;
|
|
383
|
+
}
|
|
384
|
+
if (entry.isFile()) {
|
|
385
|
+
await mkdir(path.dirname(destinationPath), { recursive: true });
|
|
386
|
+
if (await fileExists(destinationPath)) {
|
|
387
|
+
skipped.push(relativePath);
|
|
388
|
+
continue;
|
|
389
|
+
}
|
|
390
|
+
await copyFile(sourcePath, destinationPath);
|
|
391
|
+
copied.push(relativePath);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
return { copied, skipped };
|
|
395
|
+
}
|
|
396
|
+
async function resolveAzureGitApi(organization) {
|
|
397
|
+
const tokensUrl = `https://dev.azure.com/${organization}/_usersSettings/tokens`;
|
|
398
|
+
const envPat = process.env.AZURE_PAT?.trim();
|
|
399
|
+
if (envPat) {
|
|
400
|
+
try {
|
|
401
|
+
const gitApi = await createAzureGitApi(organization, envPat);
|
|
402
|
+
console.log("Using PAT from AZURE_PAT environment variable.");
|
|
403
|
+
return { gitApi };
|
|
404
|
+
}
|
|
405
|
+
catch (error) {
|
|
406
|
+
console.log("The AZURE_PAT environment variable is invalid or expired. Please provide a new PAT.");
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
while (true) {
|
|
410
|
+
const input = (await promptQuestion(`Azure DevOps Personal Access Token (create at ${tokensUrl}): `)).trim();
|
|
411
|
+
if (!input) {
|
|
412
|
+
console.log("A Personal Access Token is required to create the repository.");
|
|
413
|
+
continue;
|
|
414
|
+
}
|
|
415
|
+
try {
|
|
416
|
+
const gitApi = await createAzureGitApi(organization, input);
|
|
417
|
+
return { gitApi };
|
|
418
|
+
}
|
|
419
|
+
catch (error) {
|
|
420
|
+
console.log("The provided PAT is invalid or expired. Please try again.");
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
async function createAzureGitApi(organization, personalAccessToken) {
|
|
425
|
+
const authHandler = azdev.getPersonalAccessTokenHandler(personalAccessToken);
|
|
426
|
+
const connection = new azdev.WebApi(`https://dev.azure.com/${organization}`, authHandler);
|
|
427
|
+
await connection.connect();
|
|
428
|
+
return connection.getGitApi();
|
|
429
|
+
}
|
|
430
|
+
async function ensureAzureRepositoryExists(gitApi, params) {
|
|
431
|
+
const repositoryName = params.repository.trim();
|
|
432
|
+
if (!repositoryName) {
|
|
433
|
+
throw new Error("Repository name is required.");
|
|
434
|
+
}
|
|
435
|
+
let existingRemoteUrl;
|
|
436
|
+
let existingWebUrl;
|
|
437
|
+
try {
|
|
438
|
+
const repositories = await gitApi.getRepositories(params.project);
|
|
439
|
+
const existing = repositories?.find((repo) => repo.name?.toLowerCase() === repositoryName.toLowerCase());
|
|
440
|
+
if (existing) {
|
|
441
|
+
existingRemoteUrl = existing.remoteUrl ?? undefined;
|
|
442
|
+
existingWebUrl = existing.webUrl ?? existingRemoteUrl;
|
|
443
|
+
return { remoteUrl: existingRemoteUrl, webUrl: existingWebUrl };
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
catch (error) {
|
|
447
|
+
// Fallback to attempting creation even if listing failed (e.g., limited permissions)
|
|
448
|
+
}
|
|
449
|
+
try {
|
|
450
|
+
const created = await gitApi.createRepository({ name: repositoryName }, params.project);
|
|
451
|
+
const remoteUrl = created?.remoteUrl ?? buildAzureGitRemoteUrl(params.organization, params.project, repositoryName);
|
|
452
|
+
const webUrl = created?.webUrl ?? created?.remoteUrl ?? buildAzureRepositoryUrl(params.organization, params.project, repositoryName);
|
|
453
|
+
console.log(` Created Azure DevOps repository "${repositoryName}" in project "${params.project}".`);
|
|
454
|
+
return { remoteUrl, webUrl };
|
|
455
|
+
}
|
|
456
|
+
catch (error) {
|
|
457
|
+
try {
|
|
458
|
+
const repository = await gitApi.getRepository(repositoryName, params.project);
|
|
459
|
+
if (repository?.remoteUrl) {
|
|
460
|
+
return {
|
|
461
|
+
remoteUrl: repository.remoteUrl,
|
|
462
|
+
webUrl: repository.webUrl ?? repository.remoteUrl,
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
catch (lookupError) {
|
|
467
|
+
// Ignore lookup failure; we'll rethrow original error below.
|
|
468
|
+
}
|
|
469
|
+
const message = error.message || String(error);
|
|
470
|
+
throw new Error(`Failed to create Azure DevOps repository "${repositoryName}" in project "${params.project}": ${message}`);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
function parseAzureRemote(remoteUrl) {
|
|
474
|
+
try {
|
|
475
|
+
const url = new URL(remoteUrl);
|
|
476
|
+
const hostname = url.hostname.toLowerCase();
|
|
477
|
+
const segments = url.pathname.split("/").filter(Boolean);
|
|
478
|
+
if (hostname === "dev.azure.com") {
|
|
479
|
+
const organization = segments[0] || url.username || undefined;
|
|
480
|
+
const project = segments[1];
|
|
481
|
+
const repository = extractRepositoryFromSegments(segments.slice(2));
|
|
482
|
+
return {
|
|
483
|
+
organization: organization ? decodeURIComponent(organization) : undefined,
|
|
484
|
+
project: project ? decodeURIComponent(project) : undefined,
|
|
485
|
+
repository,
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
if (hostname.endsWith(".visualstudio.com")) {
|
|
489
|
+
const organization = hostname.replace(/\.visualstudio\.com$/, "");
|
|
490
|
+
const project = segments[0];
|
|
491
|
+
const repository = extractRepositoryFromSegments(segments.slice(1));
|
|
492
|
+
return {
|
|
493
|
+
organization,
|
|
494
|
+
project: project ? decodeURIComponent(project) : undefined,
|
|
495
|
+
repository,
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
catch (error) {
|
|
500
|
+
// Non-HTTP remote (e.g., SSH)
|
|
501
|
+
}
|
|
502
|
+
if (remoteUrl.startsWith("git@ssh.dev.azure.com:")) {
|
|
503
|
+
const [, pathPart] = remoteUrl.split(":", 2);
|
|
504
|
+
if (pathPart) {
|
|
505
|
+
const segments = pathPart.split("/").filter(Boolean);
|
|
506
|
+
// Format: v3/{organization}/{project}/{repo}
|
|
507
|
+
if (segments[0]?.toLowerCase() === "v3") {
|
|
508
|
+
return {
|
|
509
|
+
organization: segments[1] ? decodeURIComponent(segments[1]) : undefined,
|
|
510
|
+
project: segments[2] ? decodeURIComponent(segments[2]) : undefined,
|
|
511
|
+
repository: stripGitExtension(segments.slice(3).join("/")),
|
|
512
|
+
};
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
return undefined;
|
|
517
|
+
}
|
|
518
|
+
function extractRepositoryFromSegments(segments) {
|
|
519
|
+
if (!segments.length) {
|
|
520
|
+
return undefined;
|
|
521
|
+
}
|
|
522
|
+
if (segments[0] === "_git") {
|
|
523
|
+
return stripGitExtension(segments.slice(1).join("/"));
|
|
524
|
+
}
|
|
525
|
+
if (segments.length >= 2 && segments[1] === "_git") {
|
|
526
|
+
return stripGitExtension(segments.slice(2).join("/"));
|
|
527
|
+
}
|
|
528
|
+
return stripGitExtension(segments.join("/"));
|
|
529
|
+
}
|
|
530
|
+
function stripGitExtension(value) {
|
|
531
|
+
if (!value) {
|
|
532
|
+
return undefined;
|
|
533
|
+
}
|
|
534
|
+
return value.endsWith(".git") ? value.slice(0, -4) : value;
|
|
535
|
+
}
|
|
536
|
+
function encodeAzureSegment(segment) {
|
|
537
|
+
return encodeURIComponent(segment.trim());
|
|
538
|
+
}
|
|
539
|
+
function buildAzureGitRemoteUrl(organization, project, repository) {
|
|
540
|
+
const segments = [organization, project, "_git", repository].map(encodeAzureSegment);
|
|
541
|
+
return `https://dev.azure.com/${segments.join("/")}`;
|
|
542
|
+
}
|
|
543
|
+
function buildAzureRepositoryUrl(organization, project, repository) {
|
|
544
|
+
if (!organization || !project || !repository) {
|
|
545
|
+
return undefined;
|
|
546
|
+
}
|
|
547
|
+
return buildAzureGitRemoteUrl(organization, project, repository);
|
|
548
|
+
}
|
|
549
|
+
function inferRepositoryNameFromPackage(pkgName) {
|
|
550
|
+
if (typeof pkgName !== "string" || !pkgName.trim()) {
|
|
551
|
+
return "uns-app";
|
|
552
|
+
}
|
|
553
|
+
const trimmed = pkgName.trim();
|
|
554
|
+
const baseName = trimmed.startsWith("@") ? trimmed.split("/").pop() ?? trimmed : trimmed;
|
|
555
|
+
return baseName.replace(/[^A-Za-z0-9._-]+/g, "-");
|
|
556
|
+
}
|
|
557
|
+
function isGitRemoteMissingError(error) {
|
|
558
|
+
if (!error || typeof error !== "object") {
|
|
559
|
+
return false;
|
|
560
|
+
}
|
|
561
|
+
const execError = error;
|
|
562
|
+
if (typeof execError.stderr === "string") {
|
|
563
|
+
return execError.stderr.includes("No such remote");
|
|
564
|
+
}
|
|
565
|
+
return false;
|
|
566
|
+
}
|
|
567
|
+
function isGitCommandNotFoundError(error) {
|
|
568
|
+
return Boolean(error && typeof error === "object" && error.code === "ENOENT");
|
|
569
|
+
}
|
|
570
|
+
async function promptWithDefault(message, defaultValue, requiredMessage) {
|
|
571
|
+
while (true) {
|
|
572
|
+
const answer = (await promptQuestion(message)).trim();
|
|
573
|
+
if (answer) {
|
|
574
|
+
return answer;
|
|
575
|
+
}
|
|
576
|
+
if (defaultValue) {
|
|
577
|
+
return defaultValue;
|
|
578
|
+
}
|
|
579
|
+
console.log(requiredMessage);
|
|
580
|
+
}
|
|
581
|
+
}
|
|
134
582
|
async function promptQuestion(message) {
|
|
135
583
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
136
584
|
try {
|
|
@@ -140,6 +588,18 @@ async function promptQuestion(message) {
|
|
|
140
588
|
rl.close();
|
|
141
589
|
}
|
|
142
590
|
}
|
|
591
|
+
async function fileExists(filePath) {
|
|
592
|
+
try {
|
|
593
|
+
await access(filePath);
|
|
594
|
+
return true;
|
|
595
|
+
}
|
|
596
|
+
catch (error) {
|
|
597
|
+
if (error.code === "ENOENT") {
|
|
598
|
+
return false;
|
|
599
|
+
}
|
|
600
|
+
throw error;
|
|
601
|
+
}
|
|
602
|
+
}
|
|
143
603
|
async function ensureTargetDir(dir) {
|
|
144
604
|
try {
|
|
145
605
|
const stats = await stat(dir);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uns-kit/cli",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.14",
|
|
4
4
|
"description": "Command line scaffolding tool for UNS applications",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"templates"
|
|
26
26
|
],
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@uns-kit/core": "0.0.
|
|
28
|
+
"@uns-kit/core": "0.0.18"
|
|
29
29
|
},
|
|
30
30
|
"scripts": {
|
|
31
31
|
"build": "tsc -p tsconfig.build.json",
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { CodegenConfig } from '@graphql-codegen/cli';
|
|
2
|
+
import { ConfigFile } from '@uns-kit/core';
|
|
3
|
+
|
|
4
|
+
const appConfig = await ConfigFile.loadConfig();
|
|
5
|
+
|
|
6
|
+
const config: CodegenConfig = {
|
|
7
|
+
schema: appConfig.uns.graphql,
|
|
8
|
+
generates: {
|
|
9
|
+
'src/graphql/schema.ts': {
|
|
10
|
+
plugins: ['typescript', 'typescript-operations', 'typescript-resolvers'],
|
|
11
|
+
},
|
|
12
|
+
},
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export default config;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type UnsTags = string & {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type UnsTopics = string & {};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "0.2.0",
|
|
3
|
+
"configurations": [
|
|
4
|
+
{
|
|
5
|
+
"type": "node",
|
|
6
|
+
"request": "launch",
|
|
7
|
+
"name": "uns-kit: dev",
|
|
8
|
+
"runtimeArgs": [
|
|
9
|
+
"--enable-source-maps",
|
|
10
|
+
"--import",
|
|
11
|
+
"tsx"
|
|
12
|
+
],
|
|
13
|
+
"program": "${workspaceFolder}/src/index.ts",
|
|
14
|
+
"cwd": "${workspaceFolder}",
|
|
15
|
+
"sourceMaps": true,
|
|
16
|
+
"skipFiles": ["<node_internals>/**"],
|
|
17
|
+
"resolveSourceMapLocations": [
|
|
18
|
+
"${workspaceFolder}/**"
|
|
19
|
+
]
|
|
20
|
+
}
|
|
21
|
+
]
|
|
22
|
+
}
|