@uns-kit/cli 0.0.13 → 0.0.15
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 +22 -1
- package/dist/index.js +369 -27
- 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/README.md +4 -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/README.md
CHANGED
|
@@ -25,6 +25,8 @@ pnpm run dev
|
|
|
25
25
|
|
|
26
26
|
- `uns-kit create <name>` – create a new UNS project in the specified directory.
|
|
27
27
|
- `uns-kit configure-devops [path]` – add Azure DevOps tooling (dependencies, script, config) to an existing project.
|
|
28
|
+
- `uns-kit configure-vscode [path]` – copy VS Code launch/workspace files into an existing project.
|
|
29
|
+
- `uns-kit configure-codegen [path]` – scaffold GraphQL code generation and UNS refresh scripts.
|
|
28
30
|
- `uns-kit help` – display usage information.
|
|
29
31
|
|
|
30
32
|
### Configure Azure DevOps
|
|
@@ -37,7 +39,26 @@ pnpm install
|
|
|
37
39
|
pnpm run pull-request
|
|
38
40
|
```
|
|
39
41
|
|
|
40
|
-
The command prompts for your Azure DevOps organization and updates `config.json` along with the necessary dev dependencies.
|
|
42
|
+
The command prompts for your Azure DevOps organization/project, ensures the remote repository exists, and updates `config.json` along with the necessary dev dependencies.
|
|
43
|
+
|
|
44
|
+
### Configure VS Code workspace
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
uns-kit configure-vscode
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Copies `.vscode/launch.json` plus a baseline workspace file into the project. Existing files are never overwritten, making it safe to re-run after hand edits.
|
|
51
|
+
|
|
52
|
+
### Configure GraphQL code generation
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
uns-kit configure-codegen
|
|
56
|
+
pnpm install
|
|
57
|
+
pnpm run codegen
|
|
58
|
+
pnpm run refresh-uns
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Adds `codegen.ts`, seeds `src/uns/` placeholder types, and wires the GraphQL Code Generator / `refresh-uns` script into `package.json`. After installing the new dev dependencies you can regenerate strongly-typed operations (`pnpm run codegen`) and rebuild UNS topics/tags from your environment (`pnpm run refresh-uns`).
|
|
41
62
|
|
|
42
63
|
### Extend the Config Schema
|
|
43
64
|
|
package/dist/index.js
CHANGED
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { execFile } from "node:child_process";
|
|
3
|
-
import { access, cp, mkdir, readFile, readdir, stat, writeFile } from "node:fs/promises";
|
|
3
|
+
import { access, cp, mkdir, readFile, readdir, stat, writeFile, copyFile } from "node:fs/promises";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
import { createRequire } from "node:module";
|
|
7
7
|
import process from "node:process";
|
|
8
8
|
import readline from "node:readline/promises";
|
|
9
9
|
import { promisify } from "node:util";
|
|
10
|
+
import * as azdev from "azure-devops-node-api";
|
|
10
11
|
const __filename = fileURLToPath(import.meta.url);
|
|
11
12
|
const __dirname = path.dirname(__filename);
|
|
12
13
|
const require = createRequire(import.meta.url);
|
|
13
14
|
const coreVersion = resolveCoreVersion();
|
|
14
15
|
const execFileAsync = promisify(execFile);
|
|
16
|
+
const AZURE_DEVOPS_PROVIDER = "azure-devops";
|
|
15
17
|
async function main() {
|
|
16
18
|
const args = process.argv.slice(2);
|
|
17
19
|
const command = args[0];
|
|
@@ -30,6 +32,28 @@ async function main() {
|
|
|
30
32
|
}
|
|
31
33
|
return;
|
|
32
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
|
+
}
|
|
33
57
|
if (command === "create") {
|
|
34
58
|
const projectName = args[1];
|
|
35
59
|
if (!projectName) {
|
|
@@ -51,7 +75,13 @@ async function main() {
|
|
|
51
75
|
process.exitCode = 1;
|
|
52
76
|
}
|
|
53
77
|
function printHelp() {
|
|
54
|
-
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");
|
|
55
85
|
}
|
|
56
86
|
async function createProject(projectName) {
|
|
57
87
|
const targetDir = path.resolve(process.cwd(), projectName);
|
|
@@ -96,25 +126,45 @@ async function configureDevops(targetPath) {
|
|
|
96
126
|
const config = JSON.parse(configRaw);
|
|
97
127
|
await ensureGitRepository(targetDir);
|
|
98
128
|
const remoteUrl = await getGitRemoteUrl(targetDir, "origin");
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
}
|
|
106
|
-
await addGitRemote(targetDir, "origin", remoteAnswer);
|
|
107
|
-
ensuredRemoteUrl = remoteAnswer;
|
|
108
|
-
gitRemoteMessage = ` Added git remote origin -> ${remoteAnswer}`;
|
|
109
|
-
}
|
|
110
|
-
const inferredOrganization = ensuredRemoteUrl ? inferAzureOrganization(ensuredRemoteUrl) : undefined;
|
|
111
|
-
const defaultOrg = config.devops?.organization?.trim() || inferredOrganization || "sijit";
|
|
112
|
-
const answer = (await promptQuestion(`Azure DevOps organization [${defaultOrg}]: `)).trim();
|
|
113
|
-
const organization = answer || defaultOrg;
|
|
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.");
|
|
114
135
|
if (!config.devops || typeof config.devops !== "object") {
|
|
115
136
|
config.devops = {};
|
|
116
137
|
}
|
|
117
|
-
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
|
+
}
|
|
118
168
|
const requiredDevDeps = {
|
|
119
169
|
"azure-devops-node-api": "^15.1.0",
|
|
120
170
|
"simple-git": "^3.27.0",
|
|
@@ -140,7 +190,12 @@ async function configureDevops(targetPath) {
|
|
|
140
190
|
await writeFile(packagePath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
|
|
141
191
|
}
|
|
142
192
|
console.log(`\nDevOps tooling configured.`);
|
|
193
|
+
console.log(` DevOps provider: ${AZURE_DEVOPS_PROVIDER}`);
|
|
143
194
|
console.log(` Azure organization: ${organization}`);
|
|
195
|
+
console.log(` Azure project: ${project}`);
|
|
196
|
+
if (repositoryUrlMessage) {
|
|
197
|
+
console.log(repositoryUrlMessage);
|
|
198
|
+
}
|
|
144
199
|
if (gitRemoteMessage) {
|
|
145
200
|
console.log(gitRemoteMessage);
|
|
146
201
|
}
|
|
@@ -151,6 +206,107 @@ async function configureDevops(targetPath) {
|
|
|
151
206
|
console.log(" Existing package.json already contained required entries.");
|
|
152
207
|
}
|
|
153
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
|
+
}
|
|
154
310
|
async function ensureGitRepository(dir) {
|
|
155
311
|
try {
|
|
156
312
|
const { stdout } = await execFileAsync("git", ["rev-parse", "--is-inside-work-tree"], {
|
|
@@ -210,32 +366,194 @@ async function addGitRemote(dir, remoteName, remoteUrl) {
|
|
|
210
366
|
throw new Error(`Failed to add git remote origin: ${error.message}`);
|
|
211
367
|
}
|
|
212
368
|
}
|
|
213
|
-
function
|
|
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) {
|
|
214
474
|
try {
|
|
215
475
|
const url = new URL(remoteUrl);
|
|
216
476
|
const hostname = url.hostname.toLowerCase();
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
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
|
+
};
|
|
222
497
|
}
|
|
223
498
|
}
|
|
224
499
|
catch (error) {
|
|
225
|
-
//
|
|
500
|
+
// Non-HTTP remote (e.g., SSH)
|
|
226
501
|
}
|
|
227
502
|
if (remoteUrl.startsWith("git@ssh.dev.azure.com:")) {
|
|
228
503
|
const [, pathPart] = remoteUrl.split(":", 2);
|
|
229
504
|
if (pathPart) {
|
|
230
505
|
const segments = pathPart.split("/").filter(Boolean);
|
|
231
506
|
// Format: v3/{organization}/{project}/{repo}
|
|
232
|
-
if (segments
|
|
233
|
-
return
|
|
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
|
+
};
|
|
234
513
|
}
|
|
235
514
|
}
|
|
236
515
|
}
|
|
237
516
|
return undefined;
|
|
238
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
|
+
}
|
|
239
557
|
function isGitRemoteMissingError(error) {
|
|
240
558
|
if (!error || typeof error !== "object") {
|
|
241
559
|
return false;
|
|
@@ -249,6 +567,18 @@ function isGitRemoteMissingError(error) {
|
|
|
249
567
|
function isGitCommandNotFoundError(error) {
|
|
250
568
|
return Boolean(error && typeof error === "object" && error.code === "ENOENT");
|
|
251
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
|
+
}
|
|
252
582
|
async function promptQuestion(message) {
|
|
253
583
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
254
584
|
try {
|
|
@@ -258,6 +588,18 @@ async function promptQuestion(message) {
|
|
|
258
588
|
rl.close();
|
|
259
589
|
}
|
|
260
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
|
+
}
|
|
261
603
|
async function ensureTargetDir(dir) {
|
|
262
604
|
try {
|
|
263
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.15",
|
|
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.19"
|
|
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 & {};
|
|
@@ -9,6 +9,8 @@ pnpm run dev # start the local development loop
|
|
|
9
9
|
pnpm run build # emit dist/ output
|
|
10
10
|
pnpm run start # run the compiled entrypoint
|
|
11
11
|
pnpm run generate-config-schema # regenerate config.schema.json and AppConfig types
|
|
12
|
+
pnpm run codegen # regenerate typed GraphQL operations (after configure-codegen)
|
|
13
|
+
pnpm run refresh-uns # rebuild UNS topics/tags from the live schema
|
|
12
14
|
```
|
|
13
15
|
|
|
14
16
|
## Configuration
|
|
@@ -21,4 +23,6 @@ Update `config.json` with your broker, UNS URLs, and credentials. The generated
|
|
|
21
23
|
- Create MQTT proxies or Temporal workflows inside `src/index.ts`.
|
|
22
24
|
- Extend `src/config/project.config.extension.ts` with project-specific sections and run `pnpm run generate-config-schema`.
|
|
23
25
|
- Run `uns-kit configure-devops` to add the Azure DevOps pull-request tooling.
|
|
26
|
+
- Run `uns-kit configure-vscode` to copy workspace/launch configuration for VS Code.
|
|
27
|
+
- Run `uns-kit configure-codegen` to scaffold GraphQL code generation and UNS refresh scripts.
|
|
24
28
|
- Commit your new project and start building!
|
|
@@ -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
|
+
}
|