sdtk-kit 0.3.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.
- package/README.md +131 -0
- package/assets/manifest/toolkit-bundle.manifest.json +303 -0
- package/assets/manifest/toolkit-bundle.sha256.txt +59 -0
- package/assets/toolkit/toolkit/AGENTS.md +103 -0
- package/assets/toolkit/toolkit/install.ps1 +155 -0
- package/assets/toolkit/toolkit/runtimes/claude/CLAUDE_TEMPLATE.md +32 -0
- package/assets/toolkit/toolkit/runtimes/codex/CODEX_TEMPLATE.md +32 -0
- package/assets/toolkit/toolkit/scripts/init-feature.ps1 +253 -0
- package/assets/toolkit/toolkit/scripts/install-codex-skills.ps1 +181 -0
- package/assets/toolkit/toolkit/scripts/uninstall-codex-skills.ps1 +116 -0
- package/assets/toolkit/toolkit/sdtk.config.json +28 -0
- package/assets/toolkit/toolkit/sdtk.config.profiles.example.json +50 -0
- package/assets/toolkit/toolkit/skills/sdtk-api-design-spec/SKILL.md +78 -0
- package/assets/toolkit/toolkit/skills/sdtk-api-design-spec/references/API_DESIGN_CREATION_RULES.md +212 -0
- package/assets/toolkit/toolkit/skills/sdtk-api-design-spec/references/FLOWCHART_CREATION_RULES.md +397 -0
- package/assets/toolkit/toolkit/skills/sdtk-api-design-spec/scripts/generate_api_design_detail.py +565 -0
- package/assets/toolkit/toolkit/skills/sdtk-api-doc/SKILL.md +36 -0
- package/assets/toolkit/toolkit/skills/sdtk-api-doc/references/FLOWCHART_CREATION_RULES.md +397 -0
- package/assets/toolkit/toolkit/skills/sdtk-arch/SKILL.md +43 -0
- package/assets/toolkit/toolkit/skills/sdtk-arch/references/API_DESIGN_CREATION_RULES.md +212 -0
- package/assets/toolkit/toolkit/skills/sdtk-arch/references/FLOWCHART_CREATION_RULES.md +397 -0
- package/assets/toolkit/toolkit/skills/sdtk-arch/references/FLOW_ACTION_SPEC_CREATION_RULES.md +136 -0
- package/assets/toolkit/toolkit/skills/sdtk-ba/SKILL.md +24 -0
- package/assets/toolkit/toolkit/skills/sdtk-design-layout/SKILL.md +21 -0
- package/assets/toolkit/toolkit/skills/sdtk-dev/SKILL.md +20 -0
- package/assets/toolkit/toolkit/skills/sdtk-dev-backend/SKILL.md +17 -0
- package/assets/toolkit/toolkit/skills/sdtk-dev-frontend/SKILL.md +15 -0
- package/assets/toolkit/toolkit/skills/sdtk-orchestrator/SKILL.md +44 -0
- package/assets/toolkit/toolkit/skills/sdtk-pm/SKILL.md +26 -0
- package/assets/toolkit/toolkit/skills/sdtk-qa/SKILL.md +22 -0
- package/assets/toolkit/toolkit/skills/sdtk-screen-design-spec/SKILL.md +59 -0
- package/assets/toolkit/toolkit/skills/sdtk-screen-design-spec/references/FLOW_ACTION_SPEC_CREATION_RULES.md +136 -0
- package/assets/toolkit/toolkit/skills/sdtk-screen-design-spec/references/excel-image-export.md +51 -0
- package/assets/toolkit/toolkit/skills/sdtk-screen-design-spec/references/figma-mcp.md +54 -0
- package/assets/toolkit/toolkit/skills/sdtk-screen-design-spec/references/numbering-rules.md +76 -0
- package/assets/toolkit/toolkit/skills/sdtk-screen-design-spec/scripts/renumber_flow_action_spec_global.py +136 -0
- package/assets/toolkit/toolkit/skills/sdtk-screen-design-spec/scripts/validate_flow_action_spec_numbering.py +249 -0
- package/assets/toolkit/toolkit/skills/sdtk-test-case-spec/SKILL.md +65 -0
- package/assets/toolkit/toolkit/skills/sdtk-test-case-spec/references/TEST_CASE_CREATION_RULES.md +129 -0
- package/assets/toolkit/toolkit/skills/sdtk-test-case-spec/scripts/validate_test_case_spec.py +97 -0
- package/assets/toolkit/toolkit/templates/QUALITY_CHECKLIST.md +124 -0
- package/assets/toolkit/toolkit/templates/README.md +56 -0
- package/assets/toolkit/toolkit/templates/SHARED_PLANNING.md +80 -0
- package/assets/toolkit/toolkit/templates/docs/api/API_DESIGN_CREATION_RULES.md +212 -0
- package/assets/toolkit/toolkit/templates/docs/api/API_DESIGN_DETAIL_TEMPLATE.md +62 -0
- package/assets/toolkit/toolkit/templates/docs/api/API_ENDPOINTS_TEMPLATE.md +229 -0
- package/assets/toolkit/toolkit/templates/docs/api/FEATURE_API_TEMPLATE.yaml +20 -0
- package/assets/toolkit/toolkit/templates/docs/api/FLOWCHART_CREATION_RULES.md +397 -0
- package/assets/toolkit/toolkit/templates/docs/api/feature_api_flow_list_TEMPLATE.txt +12 -0
- package/assets/toolkit/toolkit/templates/docs/architecture/ARCH_DESIGN_TEMPLATE.md +109 -0
- package/assets/toolkit/toolkit/templates/docs/database/DATABASE_SPEC_TEMPLATE.md +175 -0
- package/assets/toolkit/toolkit/templates/docs/design/DESIGN_LAYOUT_TEMPLATE.md +49 -0
- package/assets/toolkit/toolkit/templates/docs/dev/FEATURE_IMPL_PLAN_TEMPLATE.md +73 -0
- package/assets/toolkit/toolkit/templates/docs/product/BACKLOG_TEMPLATE.md +50 -0
- package/assets/toolkit/toolkit/templates/docs/product/PRD_TEMPLATE.md +66 -0
- package/assets/toolkit/toolkit/templates/docs/product/PROJECT_INITIATION_TEMPLATE.md +98 -0
- package/assets/toolkit/toolkit/templates/docs/qa/QA_RELEASE_REPORT_TEMPLATE.md +61 -0
- package/assets/toolkit/toolkit/templates/docs/qa/TEST_CASE_CREATION_RULES.md +129 -0
- package/assets/toolkit/toolkit/templates/docs/qa/TEST_CASE_TEMPLATE.md +104 -0
- package/assets/toolkit/toolkit/templates/docs/specs/BA_SPEC_TEMPLATE.md +139 -0
- package/assets/toolkit/toolkit/templates/docs/specs/FLOW_ACTION_SPEC_CREATION_RULES.md +136 -0
- package/assets/toolkit/toolkit/templates/docs/specs/FLOW_ACTION_SPEC_TEMPLATE.md +160 -0
- package/bin/sdtk.js +15 -0
- package/package.json +47 -0
- package/src/commands/auth.js +85 -0
- package/src/commands/generate.js +177 -0
- package/src/commands/help.js +69 -0
- package/src/commands/init.js +73 -0
- package/src/index.js +56 -0
- package/src/lib/args.js +116 -0
- package/src/lib/errors.js +41 -0
- package/src/lib/github-access.js +68 -0
- package/src/lib/powershell.js +85 -0
- package/src/lib/state.js +83 -0
- package/src/lib/toolkit-payload.js +99 -0
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
const { parseFlags, requireFlag, validatePattern } = require("../lib/args");
|
|
6
|
+
const { verify, resolvePayloadFile } = require("../lib/toolkit-payload");
|
|
7
|
+
const { runScript } = require("../lib/powershell");
|
|
8
|
+
const { ValidationError } = require("../lib/errors");
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Expected 17 output files from generate.
|
|
12
|
+
* Placeholders: {KEY} = UPPER_SNAKE_CASE, {PASCAL} = PascalCase, {SNAKE} = lower_snake_case
|
|
13
|
+
*/
|
|
14
|
+
const EXPECTED_OUTPUT_FILES = [
|
|
15
|
+
"SHARED_PLANNING.md",
|
|
16
|
+
"QUALITY_CHECKLIST.md",
|
|
17
|
+
"docs/product/PROJECT_INITIATION_{KEY}.md",
|
|
18
|
+
"docs/specs/BA_SPEC_{KEY}.md",
|
|
19
|
+
"docs/specs/{KEY}_FLOW_ACTION_SPEC.md",
|
|
20
|
+
"docs/product/PRD_{KEY}.md",
|
|
21
|
+
"docs/product/BACKLOG_{KEY}.md",
|
|
22
|
+
"docs/architecture/ARCH_DESIGN_{KEY}.md",
|
|
23
|
+
"docs/database/DATABASE_SPEC_{KEY}.md",
|
|
24
|
+
"docs/api/{PASCAL}_API.yaml",
|
|
25
|
+
"docs/api/{KEY}_ENDPOINTS.md",
|
|
26
|
+
"docs/api/{KEY}_API_DESIGN_DETAIL.md",
|
|
27
|
+
"docs/api/{SNAKE}_api_flow_list.txt",
|
|
28
|
+
"docs/design/DESIGN_LAYOUT_{KEY}.md",
|
|
29
|
+
"docs/dev/FEATURE_IMPL_PLAN_{KEY}.md",
|
|
30
|
+
"docs/qa/{KEY}_TEST_CASE.md",
|
|
31
|
+
"docs/qa/QA_RELEASE_REPORT_{KEY}.md",
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Convert text to PascalCase.
|
|
36
|
+
* Mirrors init-feature.ps1 ConvertTo-PascalCase: split on non-alphanumeric,
|
|
37
|
+
* capitalize first letter of each part, preserve rest of casing per part.
|
|
38
|
+
* e.g. "Order Management" -> "OrderManagement", "USER_PROFILE" -> "UserProfile"
|
|
39
|
+
*/
|
|
40
|
+
function toPascalCase(text) {
|
|
41
|
+
const parts = text.split(/[^A-Za-z0-9]+/).filter((p) => p.length > 0);
|
|
42
|
+
return parts
|
|
43
|
+
.map((w) => w.charAt(0).toUpperCase() + w.slice(1))
|
|
44
|
+
.join("");
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Derive PascalCase matching init-feature.ps1 logic:
|
|
49
|
+
* prefer featureName, fallback to featureKey.
|
|
50
|
+
*/
|
|
51
|
+
function derivePascalCase(featureName, featureKey) {
|
|
52
|
+
const fromName = toPascalCase(featureName);
|
|
53
|
+
return fromName || toPascalCase(featureKey);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Convert UPPER_SNAKE_CASE to lower_snake_case.
|
|
58
|
+
*/
|
|
59
|
+
function toSnakeCase(upperSnake) {
|
|
60
|
+
return upperSnake.toLowerCase();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Expand placeholders and return list of expected file paths relative to project root.
|
|
65
|
+
*/
|
|
66
|
+
function expandExpectedFiles(featureKey, featureName) {
|
|
67
|
+
const pascal = derivePascalCase(featureName, featureKey);
|
|
68
|
+
const snake = toSnakeCase(featureKey);
|
|
69
|
+
return EXPECTED_OUTPUT_FILES.map((tmpl) =>
|
|
70
|
+
tmpl.replace(/\{KEY\}/g, featureKey).replace(/\{PASCAL\}/g, pascal).replace(/\{SNAKE\}/g, snake)
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Verify all 17 expected output files exist under projectPath.
|
|
76
|
+
* Returns list of missing files (empty if all present).
|
|
77
|
+
*/
|
|
78
|
+
function verifyOutputContract(projectPath, featureKey, featureName) {
|
|
79
|
+
const expected = expandExpectedFiles(featureKey, featureName);
|
|
80
|
+
const missing = [];
|
|
81
|
+
for (const rel of expected) {
|
|
82
|
+
const full = path.join(projectPath, rel);
|
|
83
|
+
if (!fs.existsSync(full)) {
|
|
84
|
+
missing.push(rel);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return missing;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const FLAG_DEFS = {
|
|
91
|
+
"feature-key": { type: "string" },
|
|
92
|
+
"feature-name": { type: "string" },
|
|
93
|
+
"project-path": { type: "string" },
|
|
94
|
+
force: { type: "boolean" },
|
|
95
|
+
"validate-only": { type: "boolean" },
|
|
96
|
+
verbose: { type: "boolean" },
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const FEATURE_KEY_REGEX = /^[A-Z][A-Z0-9_]*$/;
|
|
100
|
+
|
|
101
|
+
async function cmdGenerate(args) {
|
|
102
|
+
const { flags } = parseFlags(args, FLAG_DEFS);
|
|
103
|
+
|
|
104
|
+
// Validate required flags
|
|
105
|
+
const featureKey = requireFlag(flags, "feature-key", "feature-key");
|
|
106
|
+
const featureName = requireFlag(flags, "feature-name", "feature-name");
|
|
107
|
+
|
|
108
|
+
// Validate feature key format
|
|
109
|
+
validatePattern(
|
|
110
|
+
featureKey,
|
|
111
|
+
FEATURE_KEY_REGEX,
|
|
112
|
+
"feature-key",
|
|
113
|
+
"Must be UPPER_SNAKE_CASE (e.g., USER_PROFILE, ORDER_MANAGEMENT)."
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
// Resolve project path
|
|
117
|
+
const projectPath = flags["project-path"]
|
|
118
|
+
? path.resolve(flags["project-path"])
|
|
119
|
+
: process.cwd();
|
|
120
|
+
|
|
121
|
+
// Verify payload integrity before proceeding
|
|
122
|
+
verify();
|
|
123
|
+
|
|
124
|
+
// Resolve bundled init-feature.ps1
|
|
125
|
+
const generateScript = resolvePayloadFile(
|
|
126
|
+
"toolkit/scripts/init-feature.ps1"
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
// Build PowerShell parameters
|
|
130
|
+
const params = {
|
|
131
|
+
FeatureKey: featureKey,
|
|
132
|
+
FeatureName: featureName,
|
|
133
|
+
ProjectPath: projectPath,
|
|
134
|
+
};
|
|
135
|
+
if (flags.force) params.Force = true;
|
|
136
|
+
if (flags["validate-only"]) params.ValidateOnly = true;
|
|
137
|
+
|
|
138
|
+
console.log(`Generating documentation for feature: ${featureKey}`);
|
|
139
|
+
console.log(` Feature name: ${featureName}`);
|
|
140
|
+
console.log(` Project path: ${projectPath}`);
|
|
141
|
+
if (flags["validate-only"]) {
|
|
142
|
+
console.log(" Mode: validate-only (no files will be written)");
|
|
143
|
+
}
|
|
144
|
+
console.log("");
|
|
145
|
+
|
|
146
|
+
const result = await runScript(generateScript, params, { silent: !flags.verbose });
|
|
147
|
+
|
|
148
|
+
if (result.exitCode !== 0) {
|
|
149
|
+
if (result.stderr) {
|
|
150
|
+
console.error(result.stderr);
|
|
151
|
+
}
|
|
152
|
+
throw new ValidationError(
|
|
153
|
+
`Generation failed (exit code ${result.exitCode}).`
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Enforce 17-file output contract (skip for validate-only mode)
|
|
158
|
+
if (!flags["validate-only"]) {
|
|
159
|
+
const missing = verifyOutputContract(projectPath, featureKey, featureName);
|
|
160
|
+
if (missing.length > 0) {
|
|
161
|
+
console.error("\nOutput contract violation -- missing files:");
|
|
162
|
+
for (const f of missing) {
|
|
163
|
+
console.error(` - ${f}`);
|
|
164
|
+
}
|
|
165
|
+
throw new ValidationError(
|
|
166
|
+
`Expected 17 output files but ${missing.length} missing. See list above.`
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
console.log("\nAll 17 expected output files verified.");
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return 0;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
module.exports = {
|
|
176
|
+
cmdGenerate,
|
|
177
|
+
};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
function cmdHelp() {
|
|
4
|
+
const text = [
|
|
5
|
+
"SDTK CLI -- deterministic documentation toolkit",
|
|
6
|
+
"",
|
|
7
|
+
"Usage:",
|
|
8
|
+
" sdtk <command> [options]",
|
|
9
|
+
"",
|
|
10
|
+
"Commands:",
|
|
11
|
+
" init Initialize SDTK workspace (runtime adapter + config)",
|
|
12
|
+
" auth Manage GitHub authentication and entitlement",
|
|
13
|
+
" generate Generate feature documentation from templates",
|
|
14
|
+
"",
|
|
15
|
+
"Init options:",
|
|
16
|
+
" --runtime <codex|claude> Runtime adapter (default: codex)",
|
|
17
|
+
" --project-path <path> Target project directory (default: cwd)",
|
|
18
|
+
" --force Overwrite existing files",
|
|
19
|
+
" --skip-skills Skip Codex skill installation",
|
|
20
|
+
" --verbose Show detailed PowerShell script output",
|
|
21
|
+
"",
|
|
22
|
+
"Auth options:",
|
|
23
|
+
" --token <value> Store GitHub PAT",
|
|
24
|
+
" --verify Check private repo access",
|
|
25
|
+
" --status Show authentication status",
|
|
26
|
+
" --logout Clear stored credentials",
|
|
27
|
+
"",
|
|
28
|
+
"Generate options:",
|
|
29
|
+
" --feature-key <KEY> Feature identifier (UPPER_SNAKE_CASE, required)",
|
|
30
|
+
" --feature-name \"<text>\" Human-readable feature name (required)",
|
|
31
|
+
" --project-path <path> Target project directory (default: cwd)",
|
|
32
|
+
" --force Overwrite existing files",
|
|
33
|
+
" --validate-only Validate templates without writing files",
|
|
34
|
+
" --verbose Show detailed PowerShell script output",
|
|
35
|
+
"",
|
|
36
|
+
"Global options:",
|
|
37
|
+
" -h, --help Show this help",
|
|
38
|
+
" -v, --version Show version",
|
|
39
|
+
"",
|
|
40
|
+
"Exit codes:",
|
|
41
|
+
" 0 Success",
|
|
42
|
+
" 1 Validation or user error",
|
|
43
|
+
" 2 Dependency error (e.g., PowerShell not found)",
|
|
44
|
+
" 3 Integrity error (payload hash mismatch)",
|
|
45
|
+
" 4 Unexpected internal error",
|
|
46
|
+
"",
|
|
47
|
+
"Environment variables:",
|
|
48
|
+
" SDTK_ENTITLEMENT_REPO Override default entitlement repo (owner/repo format)",
|
|
49
|
+
"",
|
|
50
|
+
"Examples:",
|
|
51
|
+
" sdtk init --runtime codex",
|
|
52
|
+
" sdtk init --runtime claude --project-path ./my-project",
|
|
53
|
+
" sdtk auth --token ghp_xxxxxxxxxxxx",
|
|
54
|
+
" sdtk auth --verify",
|
|
55
|
+
' sdtk generate --feature-key USER_PROFILE --feature-name "User Profile"',
|
|
56
|
+
" sdtk generate --feature-key ORDER_MGMT --feature-name \"Order Management\" --validate-only",
|
|
57
|
+
"",
|
|
58
|
+
" # Override entitlement repo (bash/zsh):",
|
|
59
|
+
" export SDTK_ENTITLEMENT_REPO=owner/repo",
|
|
60
|
+
" # Override entitlement repo (PowerShell):",
|
|
61
|
+
' $env:SDTK_ENTITLEMENT_REPO="owner/repo"',
|
|
62
|
+
];
|
|
63
|
+
console.log(text.join("\n"));
|
|
64
|
+
return 0;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
module.exports = {
|
|
68
|
+
cmdHelp,
|
|
69
|
+
};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const path = require("path");
|
|
4
|
+
const { parseFlags, validateChoice } = require("../lib/args");
|
|
5
|
+
const { verify, resolvePayloadFile } = require("../lib/toolkit-payload");
|
|
6
|
+
const { runScript } = require("../lib/powershell");
|
|
7
|
+
const { ValidationError } = require("../lib/errors");
|
|
8
|
+
|
|
9
|
+
const FLAG_DEFS = {
|
|
10
|
+
runtime: { type: "string" },
|
|
11
|
+
"project-path": { type: "string" },
|
|
12
|
+
force: { type: "boolean" },
|
|
13
|
+
"skip-skills": { type: "boolean" },
|
|
14
|
+
verbose: { type: "boolean" },
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const VALID_RUNTIMES = ["codex", "claude"];
|
|
18
|
+
|
|
19
|
+
async function cmdInit(args) {
|
|
20
|
+
const { flags } = parseFlags(args, FLAG_DEFS);
|
|
21
|
+
|
|
22
|
+
// Validate runtime
|
|
23
|
+
const runtime = flags.runtime || "codex";
|
|
24
|
+
validateChoice(runtime, VALID_RUNTIMES, "runtime");
|
|
25
|
+
|
|
26
|
+
// Resolve project path
|
|
27
|
+
const projectPath = flags["project-path"]
|
|
28
|
+
? path.resolve(flags["project-path"])
|
|
29
|
+
: process.cwd();
|
|
30
|
+
|
|
31
|
+
// Verify payload integrity before proceeding
|
|
32
|
+
verify();
|
|
33
|
+
|
|
34
|
+
// Resolve bundled install.ps1
|
|
35
|
+
const installScript = resolvePayloadFile("toolkit/install.ps1");
|
|
36
|
+
|
|
37
|
+
// Build PowerShell parameters
|
|
38
|
+
const params = {
|
|
39
|
+
ProjectPath: projectPath,
|
|
40
|
+
Runtime: runtime,
|
|
41
|
+
};
|
|
42
|
+
if (flags.force) params.Force = true;
|
|
43
|
+
if (flags["skip-skills"]) params.SkipSkills = true;
|
|
44
|
+
if (!flags.verbose) params.Quiet = true;
|
|
45
|
+
|
|
46
|
+
console.log(`Initializing SDTK workspace...`);
|
|
47
|
+
console.log(` Runtime: ${runtime}`);
|
|
48
|
+
console.log(` Project: ${projectPath}`);
|
|
49
|
+
console.log("");
|
|
50
|
+
|
|
51
|
+
const result = await runScript(installScript, params, { silent: !flags.verbose });
|
|
52
|
+
|
|
53
|
+
if (result.exitCode !== 0) {
|
|
54
|
+
if (result.stderr) {
|
|
55
|
+
console.error(result.stderr);
|
|
56
|
+
}
|
|
57
|
+
throw new ValidationError(
|
|
58
|
+
`Initialization failed (exit code ${result.exitCode}).`
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
console.log("");
|
|
63
|
+
console.log("SDTK workspace initialized successfully.");
|
|
64
|
+
console.log("");
|
|
65
|
+
console.log("Next steps:");
|
|
66
|
+
console.log(" 1. Review and customize sdtk.config.json for your project stack.");
|
|
67
|
+
console.log(' 2. Run "sdtk generate --feature-key <KEY> --feature-name <name>" to scaffold docs.');
|
|
68
|
+
return 0;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
module.exports = {
|
|
72
|
+
cmdInit,
|
|
73
|
+
};
|
package/src/index.js
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const { cmdHelp } = require("./commands/help");
|
|
4
|
+
const { cmdInit } = require("./commands/init");
|
|
5
|
+
const { cmdAuth } = require("./commands/auth");
|
|
6
|
+
const { cmdGenerate } = require("./commands/generate");
|
|
7
|
+
const { ValidationError, CliError } = require("./lib/errors");
|
|
8
|
+
|
|
9
|
+
function getVersion() {
|
|
10
|
+
const pkg = require("../package.json");
|
|
11
|
+
return pkg.version;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function parseCommand(argv) {
|
|
15
|
+
if (!argv || argv.length === 0) {
|
|
16
|
+
return { command: "help", args: [] };
|
|
17
|
+
}
|
|
18
|
+
const [first, ...rest] = argv;
|
|
19
|
+
if (first === "-h" || first === "--help") {
|
|
20
|
+
return { command: "help", args: [] };
|
|
21
|
+
}
|
|
22
|
+
if (first === "-v" || first === "--version") {
|
|
23
|
+
return { command: "version", args: [] };
|
|
24
|
+
}
|
|
25
|
+
return { command: first, args: rest };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const COMMANDS = new Set(["help", "version", "init", "auth", "generate"]);
|
|
29
|
+
|
|
30
|
+
async function run(argv) {
|
|
31
|
+
const { command, args } = parseCommand(argv);
|
|
32
|
+
|
|
33
|
+
if (!COMMANDS.has(command)) {
|
|
34
|
+
throw new ValidationError(
|
|
35
|
+
`Unknown command: "${command}". Run "sdtk --help" for available commands.`
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
switch (command) {
|
|
40
|
+
case "help":
|
|
41
|
+
return cmdHelp();
|
|
42
|
+
case "version":
|
|
43
|
+
console.log(`sdtk-kit ${getVersion()}`);
|
|
44
|
+
return 0;
|
|
45
|
+
case "init":
|
|
46
|
+
return cmdInit(args);
|
|
47
|
+
case "auth":
|
|
48
|
+
return cmdAuth(args);
|
|
49
|
+
case "generate":
|
|
50
|
+
return cmdGenerate(args);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
module.exports = {
|
|
55
|
+
run,
|
|
56
|
+
};
|
package/src/lib/args.js
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const { ValidationError } = require("./errors");
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Parse argv into a structured flags object.
|
|
7
|
+
* Supports: --flag value, --flag=value, --bool-flag (no value).
|
|
8
|
+
*
|
|
9
|
+
* @param {string[]} argv - Raw arguments (after command is stripped).
|
|
10
|
+
* @param {Object<string, {type: 'string'|'boolean', required?: boolean, alias?: string}>} defs - Flag definitions.
|
|
11
|
+
* @returns {{ flags: Object, positional: string[] }}
|
|
12
|
+
*/
|
|
13
|
+
function parseFlags(argv, defs) {
|
|
14
|
+
const flags = {};
|
|
15
|
+
const positional = [];
|
|
16
|
+
const aliasMap = {};
|
|
17
|
+
|
|
18
|
+
for (const [name, def] of Object.entries(defs)) {
|
|
19
|
+
if (def.alias) aliasMap[def.alias] = name;
|
|
20
|
+
if (def.type === "boolean") flags[name] = false;
|
|
21
|
+
else flags[name] = undefined;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
let i = 0;
|
|
25
|
+
while (i < argv.length) {
|
|
26
|
+
const arg = argv[i];
|
|
27
|
+
|
|
28
|
+
if (!arg.startsWith("-")) {
|
|
29
|
+
positional.push(arg);
|
|
30
|
+
i++;
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
let key;
|
|
35
|
+
let inlineValue;
|
|
36
|
+
|
|
37
|
+
if (arg.includes("=")) {
|
|
38
|
+
const eqIdx = arg.indexOf("=");
|
|
39
|
+
key = arg.slice(0, eqIdx);
|
|
40
|
+
inlineValue = arg.slice(eqIdx + 1);
|
|
41
|
+
} else {
|
|
42
|
+
key = arg;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Normalize: strip leading dashes, resolve alias
|
|
46
|
+
const stripped = key.replace(/^-{1,2}/, "");
|
|
47
|
+
const resolved = aliasMap[stripped] || stripped;
|
|
48
|
+
|
|
49
|
+
if (!(resolved in defs)) {
|
|
50
|
+
throw new ValidationError(`Unknown flag: ${key}`);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const def = defs[resolved];
|
|
54
|
+
|
|
55
|
+
if (def.type === "boolean") {
|
|
56
|
+
flags[resolved] = true;
|
|
57
|
+
i++;
|
|
58
|
+
} else {
|
|
59
|
+
// string type
|
|
60
|
+
let value = inlineValue;
|
|
61
|
+
if (value === undefined) {
|
|
62
|
+
i++;
|
|
63
|
+
if (i >= argv.length) {
|
|
64
|
+
throw new ValidationError(`Flag ${key} requires a value.`);
|
|
65
|
+
}
|
|
66
|
+
value = argv[i];
|
|
67
|
+
}
|
|
68
|
+
flags[resolved] = value;
|
|
69
|
+
i++;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return { flags, positional };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Require a flag to be present (non-undefined, non-empty for strings).
|
|
78
|
+
*/
|
|
79
|
+
function requireFlag(flags, name, label) {
|
|
80
|
+
const val = flags[name];
|
|
81
|
+
if (val === undefined || val === null || val === "") {
|
|
82
|
+
throw new ValidationError(`Missing required flag: --${label || name}`);
|
|
83
|
+
}
|
|
84
|
+
return val;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Validate a value is one of the allowed choices.
|
|
89
|
+
*/
|
|
90
|
+
function validateChoice(value, choices, label) {
|
|
91
|
+
if (!choices.includes(value)) {
|
|
92
|
+
throw new ValidationError(
|
|
93
|
+
`Invalid value for --${label}: "${value}". Must be one of: ${choices.join(", ")}`
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
return value;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Validate a value matches a regex pattern.
|
|
101
|
+
*/
|
|
102
|
+
function validatePattern(value, regex, label, hint) {
|
|
103
|
+
if (!regex.test(value)) {
|
|
104
|
+
throw new ValidationError(
|
|
105
|
+
`Invalid value for --${label}: "${value}". ${hint || `Must match ${regex}`}`
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
return value;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
module.exports = {
|
|
112
|
+
parseFlags,
|
|
113
|
+
requireFlag,
|
|
114
|
+
validateChoice,
|
|
115
|
+
validatePattern,
|
|
116
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
class CliError extends Error {
|
|
4
|
+
constructor(message, exitCode = 4) {
|
|
5
|
+
super(message);
|
|
6
|
+
this.name = this.constructor.name;
|
|
7
|
+
this.exitCode = exitCode;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
class ValidationError extends CliError {
|
|
12
|
+
constructor(message) {
|
|
13
|
+
super(message, 1);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
class DependencyError extends CliError {
|
|
18
|
+
constructor(message) {
|
|
19
|
+
super(message, 2);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
class IntegrityError extends CliError {
|
|
24
|
+
constructor(message) {
|
|
25
|
+
super(message, 3);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
class InternalError extends CliError {
|
|
30
|
+
constructor(message) {
|
|
31
|
+
super(message, 4);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
module.exports = {
|
|
36
|
+
CliError,
|
|
37
|
+
ValidationError,
|
|
38
|
+
DependencyError,
|
|
39
|
+
IntegrityError,
|
|
40
|
+
InternalError,
|
|
41
|
+
};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const { ValidationError } = require("./errors");
|
|
4
|
+
|
|
5
|
+
// Entitlement repo: configurable via env var, falls back to default.
|
|
6
|
+
// Set SDTK_ENTITLEMENT_REPO=owner/repo to override.
|
|
7
|
+
const DEFAULT_REPO = "DucTN/sdtk-private";
|
|
8
|
+
|
|
9
|
+
function getEntitlementRepo() {
|
|
10
|
+
return process.env.SDTK_ENTITLEMENT_REPO || DEFAULT_REPO;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Check if a GitHub token has access to the private distribution repo.
|
|
15
|
+
*
|
|
16
|
+
* @param {string} token - GitHub PAT.
|
|
17
|
+
* @param {string} [repo] - Repo in "owner/name" format. Defaults to SDTK_ENTITLEMENT_REPO env or built-in default.
|
|
18
|
+
* @returns {Promise<{hasAccess: boolean, message: string}>}
|
|
19
|
+
*/
|
|
20
|
+
async function checkRepoAccess(token, repo) {
|
|
21
|
+
const targetRepo = repo || getEntitlementRepo();
|
|
22
|
+
const url = `https://api.github.com/repos/${targetRepo}`;
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
const response = await fetch(url, {
|
|
26
|
+
headers: {
|
|
27
|
+
Authorization: `Bearer ${token}`,
|
|
28
|
+
Accept: "application/vnd.github.v3+json",
|
|
29
|
+
"User-Agent": "sdtk-cli",
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
if (response.status === 200) {
|
|
34
|
+
return {
|
|
35
|
+
hasAccess: true,
|
|
36
|
+
message: `Access verified for ${targetRepo}.`,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (response.status === 404 || response.status === 403) {
|
|
41
|
+
return {
|
|
42
|
+
hasAccess: false,
|
|
43
|
+
message: `No access to ${targetRepo}. Check your token permissions.`,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (response.status === 401) {
|
|
48
|
+
return {
|
|
49
|
+
hasAccess: false,
|
|
50
|
+
message: "Token is invalid or expired.",
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
hasAccess: false,
|
|
56
|
+
message: `Unexpected response (HTTP ${response.status}) from GitHub API.`,
|
|
57
|
+
};
|
|
58
|
+
} catch (err) {
|
|
59
|
+
throw new ValidationError(
|
|
60
|
+
`Failed to reach GitHub API: ${err.message}\nCheck your network connection.`
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
module.exports = {
|
|
66
|
+
checkRepoAccess,
|
|
67
|
+
getEntitlementRepo,
|
|
68
|
+
};
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const { execFile } = require("child_process");
|
|
4
|
+
const { DependencyError } = require("./errors");
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Find available PowerShell executable.
|
|
8
|
+
* Prefers pwsh (PowerShell Core) over powershell.exe (Windows PowerShell).
|
|
9
|
+
* @returns {string} PowerShell executable name.
|
|
10
|
+
*/
|
|
11
|
+
function findPowerShell() {
|
|
12
|
+
// On Windows, powershell.exe is always available
|
|
13
|
+
// pwsh (PowerShell Core) is preferred if available
|
|
14
|
+
if (process.platform === "win32") {
|
|
15
|
+
return "powershell.exe";
|
|
16
|
+
}
|
|
17
|
+
// On non-Windows, pwsh must be installed
|
|
18
|
+
return "pwsh";
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Execute a PowerShell script file with arguments.
|
|
23
|
+
*
|
|
24
|
+
* @param {string} scriptPath - Absolute path to the .ps1 file.
|
|
25
|
+
* @param {Object<string, string|boolean>} params - Parameters to pass.
|
|
26
|
+
* String values become `-ParamName "value"`, boolean true becomes `-ParamName`.
|
|
27
|
+
* @param {Object} [options] - Optional settings.
|
|
28
|
+
* @param {boolean} [options.silent] - If true, suppress stdout output.
|
|
29
|
+
* @returns {Promise<{exitCode: number, stdout: string, stderr: string}>}
|
|
30
|
+
*/
|
|
31
|
+
function runScript(scriptPath, params = {}, options = {}) {
|
|
32
|
+
return new Promise((resolve, reject) => {
|
|
33
|
+
const psExe = findPowerShell();
|
|
34
|
+
const args = [
|
|
35
|
+
"-ExecutionPolicy",
|
|
36
|
+
"Bypass",
|
|
37
|
+
"-NoProfile",
|
|
38
|
+
"-NonInteractive",
|
|
39
|
+
"-File",
|
|
40
|
+
scriptPath,
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
// Build parameters safely using execFile array args
|
|
44
|
+
for (const [key, value] of Object.entries(params)) {
|
|
45
|
+
if (value === true) {
|
|
46
|
+
args.push(`-${key}`);
|
|
47
|
+
} else if (value === false || value === undefined || value === null) {
|
|
48
|
+
// Skip false/undefined/null switches
|
|
49
|
+
} else {
|
|
50
|
+
args.push(`-${key}`);
|
|
51
|
+
args.push(String(value));
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
execFile(psExe, args, { maxBuffer: 10 * 1024 * 1024 }, (error, stdout, stderr) => {
|
|
56
|
+
if (error && error.code === "ENOENT") {
|
|
57
|
+
reject(
|
|
58
|
+
new DependencyError(
|
|
59
|
+
`PowerShell not found. Ensure PowerShell is installed and available in PATH.\n` +
|
|
60
|
+
`Tried: ${psExe}`
|
|
61
|
+
)
|
|
62
|
+
);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const exitCode = error ? error.code || 1 : 0;
|
|
67
|
+
const result = {
|
|
68
|
+
exitCode: typeof exitCode === "number" ? exitCode : 1,
|
|
69
|
+
stdout: stdout || "",
|
|
70
|
+
stderr: stderr || "",
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
if (!options.silent && result.stdout) {
|
|
74
|
+
process.stdout.write(result.stdout);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
resolve(result);
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
module.exports = {
|
|
83
|
+
runScript,
|
|
84
|
+
findPowerShell,
|
|
85
|
+
};
|