@o3co/create-auth-provider 0.4.1

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.
Files changed (39) hide show
  1. package/README.ja.md +107 -0
  2. package/README.md +107 -0
  3. package/dist/__tests__/index.test.d.mts +2 -0
  4. package/dist/__tests__/index.test.d.mts.map +1 -0
  5. package/dist/__tests__/index.test.mjs +226 -0
  6. package/dist/cli.d.mts +3 -0
  7. package/dist/cli.d.mts.map +1 -0
  8. package/dist/cli.mjs +18 -0
  9. package/dist/index.d.mts +5 -0
  10. package/dist/index.d.mts.map +1 -0
  11. package/dist/index.mjs +166 -0
  12. package/package.json +28 -0
  13. package/templates/standalone/.dockerignore +9 -0
  14. package/templates/standalone/.env.example +21 -0
  15. package/templates/standalone/Dockerfile +58 -0
  16. package/templates/standalone/Makefile +12 -0
  17. package/templates/standalone/README.ja.md +205 -0
  18. package/templates/standalone/README.md +205 -0
  19. package/templates/standalone/config/application.conf +132 -0
  20. package/templates/standalone/config/clients.yaml +2 -0
  21. package/templates/standalone/config/clients.yaml.example +14 -0
  22. package/templates/standalone/config/development.conf +8 -0
  23. package/templates/standalone/config/production.conf +10 -0
  24. package/templates/standalone/docker-compose.test.yml +16 -0
  25. package/templates/standalone/docker-compose.yml +26 -0
  26. package/templates/standalone/package.json +45 -0
  27. package/templates/standalone/src/__tests__/configPath.test.mts +50 -0
  28. package/templates/standalone/src/__tests__/smoke.test.mts +204 -0
  29. package/templates/standalone/src/app.mts +201 -0
  30. package/templates/standalone/src/configPath.mts +37 -0
  31. package/templates/standalone/src/logger.mts +18 -0
  32. package/templates/standalone/tests/Makefile +5 -0
  33. package/templates/standalone/tests/did-grant.test.js +193 -0
  34. package/templates/standalone/tests/index.test.js +218 -0
  35. package/templates/standalone/tests/package-lock.json +2225 -0
  36. package/templates/standalone/tests/package.json +17 -0
  37. package/templates/standalone/tsconfig.json +12 -0
  38. package/templates/standalone/vitest.config.mts +7 -0
  39. package/templates/versions.json +9 -0
package/README.ja.md ADDED
@@ -0,0 +1,107 @@
1
+ # @o3co/create-auth-provider
2
+
3
+ auth.provider 用の CLI スキャフォルダーです。standalone テンプレートから新しいプロジェクトを生成します。
4
+
5
+ ## 使い方
6
+
7
+ ```bash
8
+ npx @o3co/create-auth-provider <project-name> [--dir <dir-name>]
9
+ ```
10
+
11
+ `<project-name>` はスコープ付き npm 名 (`@scope/pkg`) とスコープなしの名前 (`pkg`) のどちらでも指定できます。
12
+
13
+ スコープなしの例:
14
+
15
+ ```bash
16
+ npx @o3co/create-auth-provider my-auth-server
17
+ cd my-auth-server
18
+ npm install
19
+ npm run debug
20
+ ```
21
+
22
+ スコープ付きの例(ディレクトリ名はパッケージ部分 `auth.provider` がデフォルト):
23
+
24
+ ```bash
25
+ npx @o3co/create-auth-provider @my-org/auth.provider
26
+ cd auth.provider
27
+ npm install
28
+ npm run debug
29
+ ```
30
+
31
+ `--dir` でディレクトリ名を明示指定:
32
+
33
+ ```bash
34
+ npx @o3co/create-auth-provider @my-org/auth.provider --dir provider
35
+ cd provider
36
+ ```
37
+
38
+ ## 動作内容
39
+
40
+ 1. `<project-name>` を検証する(バリデーションルール参照)。
41
+ 2. 生成先ディレクトリ名を決定する: `--dir <value>` が指定されていればその値、そうでなければスコープ付き名のパッケージ部分、最終的には入力値そのもの。
42
+ 3. 生成先ディレクトリを `<cwd>/<dir-name>` として解決する。
43
+ 4. ディレクトリがすでに存在する場合はエラーを出力して終了する。
44
+ 5. `templates/standalone/` を生成先ディレクトリにコピーする(`node_modules/` と `dist/` は除外)。
45
+ 6. 生成されたディレクトリの `package.json` を書き換える:
46
+ - `name` を `<project-name>` をそのまま設定する(スコープを保持)。
47
+ - `private` フィールドを削除する。
48
+ - すべての `workspace:*` バージョン参照を `versions.json` の公開 semver バージョンに置き換える。
49
+ 7. 次のステップを出力する。
50
+
51
+ ## バリデーションルール
52
+
53
+ `<project-name>` は以下のいずれかに一致する必要があります:
54
+
55
+ - スコープなし: `^[a-z0-9][a-z0-9-._~]*$`
56
+ - スコープ付き: `^@[a-z0-9][a-z0-9-._~]*/[a-z0-9][a-z0-9-._~]*$`
57
+
58
+ いずれも空文字・`.`・`..` は不可、最大 214 文字。
59
+
60
+ `--dir <value>` はスコープなしのパターンと同じ制約です。
61
+
62
+ ## 既知の制約
63
+
64
+ 内包されているテンプレートの `README.md` / `README.ja.md` の見出しは `@o3co/auth-provider-standalone` のままです。スコープ付きでプロジェクトを生成した場合、この見出しは生成された `package.json` の `name` と一致しません。必要に応じて手動で修正してください。
65
+
66
+ ## 生成される構造
67
+
68
+ ```
69
+ <プロジェクト名>/
70
+ ├── config/
71
+ │ ├── application.conf # HOCON 設定(環境変数で上書き可能)
72
+ │ ├── clients.yaml # OAuth クライアントレジストリ
73
+ │ └── clients.yaml.example
74
+ ├── src/
75
+ │ └── app.mts # Composition root
76
+ ├── tests/
77
+ ├── .dockerignore
78
+ ├── .env.example
79
+ ├── Dockerfile
80
+ ├── docker-compose.yml
81
+ ├── docker-compose.test.yml
82
+ ├── Makefile
83
+ ├── package.json
84
+ ├── tsconfig.json
85
+ └── vitest.config.mts
86
+ ```
87
+
88
+ 生成後、`.env.example` をコピーして `.env` を作成し、必須の環境変数を設定してください。
89
+
90
+ ## プログラマティック API
91
+
92
+ ```typescript
93
+ import { scaffold, main } from "@o3co/create-auth-provider";
94
+
95
+ // 絶対パスにプロジェクトを生成する
96
+ scaffold(targetDir: string, projectName: string): void;
97
+
98
+ // CLI エントリーポイント — process.argv を読み取り、エラー時は終了する
99
+ main(): void;
100
+ ```
101
+
102
+ テンプレートディレクトリが見つからない場合、または `workspace:*` 依存関係が `versions.json` で解決できない場合、`scaffold` はエラーをスローします。
103
+
104
+ ## 関連
105
+
106
+ - [`@o3co/auth-provider-standalone`](../templates/standalone) — このツールが生成元とするテンプレート
107
+ - [`@o3co/auth-provider-core`](../packages/core) — コアアプリケーションファクトリ
package/README.md ADDED
@@ -0,0 +1,107 @@
1
+ # @o3co/create-auth-provider
2
+
3
+ CLI scaffolder for auth.provider. Generates a new project from the standalone template.
4
+
5
+ ## Usage
6
+
7
+ ```bash
8
+ npx @o3co/create-auth-provider <project-name> [--dir <dir-name>]
9
+ ```
10
+
11
+ `<project-name>` may be either a scoped npm name (`@scope/pkg`) or an unscoped name (`pkg`).
12
+
13
+ Unscoped example:
14
+
15
+ ```bash
16
+ npx @o3co/create-auth-provider my-auth-server
17
+ cd my-auth-server
18
+ npm install
19
+ npm run debug
20
+ ```
21
+
22
+ Scoped example (directory defaults to the package portion `auth.provider`):
23
+
24
+ ```bash
25
+ npx @o3co/create-auth-provider @my-org/auth.provider
26
+ cd auth.provider
27
+ npm install
28
+ npm run debug
29
+ ```
30
+
31
+ Override the directory name with `--dir`:
32
+
33
+ ```bash
34
+ npx @o3co/create-auth-provider @my-org/auth.provider --dir provider
35
+ cd provider
36
+ ```
37
+
38
+ ## What It Does
39
+
40
+ 1. Validates `<project-name>` (see Validation Rules).
41
+ 2. Derives the target directory name: `--dir <value>` if given, else the unscoped part of a scoped name, else the name itself.
42
+ 3. Resolves the target directory as `<cwd>/<dir-name>`.
43
+ 4. Errors if the directory already exists.
44
+ 5. Copies `templates/standalone/` to the target directory, excluding `node_modules/` and `dist/`.
45
+ 6. Rewrites `package.json` in the generated directory:
46
+ - Sets `name` to `<project-name>` verbatim (scope-preserving).
47
+ - Removes the `private` field.
48
+ - Replaces all `workspace:*` version references with published semver versions from `versions.json`.
49
+ 7. Prints next-step instructions.
50
+
51
+ ## Validation Rules
52
+
53
+ `<project-name>` must match one of:
54
+
55
+ - Unscoped: `^[a-z0-9][a-z0-9-._~]*$`
56
+ - Scoped: `^@[a-z0-9][a-z0-9-._~]*/[a-z0-9][a-z0-9-._~]*$`
57
+
58
+ Both forms must be non-empty, not `.` or `..`, and ≤ 214 characters.
59
+
60
+ `--dir <value>` must match the unscoped pattern above (same constraints).
61
+
62
+ ## Known Limitations
63
+
64
+ The bundled template's `README.md` / `README.ja.md` still carry the upstream title `@o3co/auth-provider-standalone`. When generating a scoped project, that title will not match your `package.json` name; edit it manually if it matters for your use case.
65
+
66
+ ## Generated Structure
67
+
68
+ ```
69
+ <project-name>/
70
+ ├── config/
71
+ │ ├── application.conf # HOCON config (overridable via env vars)
72
+ │ ├── clients.yaml # OAuth client registry
73
+ │ └── clients.yaml.example
74
+ ├── src/
75
+ │ └── app.mts # Composition root
76
+ ├── tests/
77
+ ├── .dockerignore
78
+ ├── .env.example
79
+ ├── Dockerfile
80
+ ├── docker-compose.yml
81
+ ├── docker-compose.test.yml
82
+ ├── Makefile
83
+ ├── package.json
84
+ ├── tsconfig.json
85
+ └── vitest.config.mts
86
+ ```
87
+
88
+ After generation, fill in the required environment variables in `.env` (copy from `.env.example`).
89
+
90
+ ## Programmatic API
91
+
92
+ ```typescript
93
+ import { scaffold, main } from "@o3co/create-auth-provider";
94
+
95
+ // Generate a project at an absolute path
96
+ scaffold(targetDir: string, projectName: string): void;
97
+
98
+ // CLI entry point — reads process.argv and exits on error
99
+ main(): void;
100
+ ```
101
+
102
+ `scaffold` throws if the template directory is missing or if a `workspace:*` dependency cannot be resolved in `versions.json`.
103
+
104
+ ## See Also
105
+
106
+ - [`@o3co/auth-provider-standalone`](../templates/standalone) — The template this tool generates from
107
+ - [`@o3co/auth-provider-core`](../packages/core) — Core application factory
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=index.test.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.test.d.mts","sourceRoot":"","sources":["../../src/__tests__/index.test.mts"],"names":[],"mappings":""}
@@ -0,0 +1,226 @@
1
+ import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync } from "node:fs";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
5
+ import { isValidDirName, isValidProjectName, main, scaffold } from "../index.mjs";
6
+ describe("scaffold", () => {
7
+ let tempDir;
8
+ beforeEach(() => {
9
+ tempDir = mkdtempSync(join(tmpdir(), "create-auth-provider-test-"));
10
+ });
11
+ afterEach(() => {
12
+ rmSync(tempDir, { recursive: true, force: true });
13
+ });
14
+ it("copies template files to target directory", () => {
15
+ const targetDir = join(tempDir, "my-auth");
16
+ scaffold(targetDir, "my-auth");
17
+ expect(existsSync(join(targetDir, "package.json"))).toBe(true);
18
+ expect(existsSync(join(targetDir, "tsconfig.json"))).toBe(true);
19
+ expect(existsSync(join(targetDir, "src", "app.mts"))).toBe(true);
20
+ expect(existsSync(join(targetDir, "config", "application.conf"))).toBe(true);
21
+ expect(existsSync(join(targetDir, "config", "development.conf"))).toBe(true);
22
+ expect(existsSync(join(targetDir, "config", "production.conf"))).toBe(true);
23
+ });
24
+ it("rewrites package.json name to project name", () => {
25
+ const targetDir = join(tempDir, "my-auth");
26
+ scaffold(targetDir, "my-auth");
27
+ const pkg = JSON.parse(readFileSync(join(targetDir, "package.json"), "utf-8"));
28
+ expect(pkg.name).toBe("my-auth");
29
+ });
30
+ it("replaces all workspace:* dependencies with caret versions", () => {
31
+ const targetDir = join(tempDir, "my-auth");
32
+ scaffold(targetDir, "my-auth");
33
+ const pkg = JSON.parse(readFileSync(join(targetDir, "package.json"), "utf-8"));
34
+ // No workspace:* should remain anywhere
35
+ for (const section of ["dependencies", "devDependencies", "peerDependencies"]) {
36
+ const deps = pkg[section];
37
+ if (!deps)
38
+ continue;
39
+ for (const [name, version] of Object.entries(deps)) {
40
+ expect(version, `${section}.${name} should not be workspace:*`).not.toBe("workspace:*");
41
+ }
42
+ }
43
+ // Specific checks
44
+ expect(pkg.dependencies["@o3co/auth-provider-core"]).toMatch(/^\^/);
45
+ expect(pkg.dependencies["@o3co/auth-provider-federation-google"]).toMatch(/^\^/);
46
+ expect(pkg.dependencies["@o3co/auth-provider-federation-github"]).toBeUndefined();
47
+ expect(pkg.dependencies["@o3co/auth-provider-foundation"]).toMatch(/^\^/);
48
+ });
49
+ it("removes private field from package.json", () => {
50
+ const targetDir = join(tempDir, "my-auth");
51
+ scaffold(targetDir, "my-auth");
52
+ const pkg = JSON.parse(readFileSync(join(targetDir, "package.json"), "utf-8"));
53
+ expect(pkg.private).toBeUndefined();
54
+ });
55
+ it("writes scoped project name verbatim into package.json", () => {
56
+ const targetDir = join(tempDir, "auth.provider");
57
+ scaffold(targetDir, "@piratis-blossoms/auth.provider");
58
+ const pkg = JSON.parse(readFileSync(join(targetDir, "package.json"), "utf-8"));
59
+ expect(pkg.name).toBe("@piratis-blossoms/auth.provider");
60
+ });
61
+ it("ships versions for optional federation provider packages", () => {
62
+ const versions = JSON.parse(readFileSync(join("templates", "versions.json"), "utf-8"));
63
+ expect(versions["@o3co/auth-provider-federation-google"]).toBe("0.0.0");
64
+ expect(versions["@o3co/auth-provider-federation-github"]).toBe("0.0.0");
65
+ });
66
+ });
67
+ describe("isValidProjectName", () => {
68
+ it.each([
69
+ ["my-auth"],
70
+ ["auth.provider"],
71
+ ["a"],
72
+ ["foo_bar~baz.1"],
73
+ ["@piratis-blossoms/auth.provider"],
74
+ ["@foo-bar/baz_qux~1"],
75
+ ])("accepts %s", (name) => {
76
+ expect(isValidProjectName(name)).toBe(true);
77
+ });
78
+ it.each([
79
+ [""],
80
+ ["."],
81
+ [".."],
82
+ ["UPPER"],
83
+ ["with space"],
84
+ ["with/slash"],
85
+ ["with\\back"],
86
+ ["@"],
87
+ ["@/"],
88
+ ["@scope"],
89
+ ["@/pkg"],
90
+ ["@scope/"],
91
+ ["@scope//pkg"],
92
+ ["@SCOPE/pkg"],
93
+ ["a".repeat(215)],
94
+ ])("rejects %s", (name) => {
95
+ expect(isValidProjectName(name)).toBe(false);
96
+ });
97
+ });
98
+ describe("isValidDirName", () => {
99
+ it.each([["my-auth"], ["auth.provider"], ["a"], ["foo_bar~baz.1"]])("accepts %s", (name) => {
100
+ expect(isValidDirName(name)).toBe(true);
101
+ });
102
+ it.each([
103
+ [""],
104
+ ["."],
105
+ [".."],
106
+ ["@scope/pkg"],
107
+ ["with/slash"],
108
+ ["with\\back"],
109
+ ["@piratis-blossoms"],
110
+ ["UPPER"],
111
+ ["a".repeat(215)],
112
+ ])("rejects %s", (name) => {
113
+ expect(isValidDirName(name)).toBe(false);
114
+ });
115
+ });
116
+ describe("main (argv parsing and directory derivation)", () => {
117
+ let cwdBackup;
118
+ let workdir;
119
+ let argvBackup;
120
+ beforeEach(() => {
121
+ cwdBackup = process.cwd();
122
+ workdir = mkdtempSync(join(tmpdir(), "create-auth-provider-main-"));
123
+ process.chdir(workdir);
124
+ argvBackup = process.argv;
125
+ });
126
+ afterEach(() => {
127
+ process.chdir(cwdBackup);
128
+ process.argv = argvBackup;
129
+ rmSync(workdir, { recursive: true, force: true });
130
+ });
131
+ const runMain = (args) => {
132
+ process.argv = ["node", "cli", ...args];
133
+ const exitSpy = vi.spyOn(process, "exit").mockImplementation(((code) => {
134
+ throw new Error(`__exit__:${code ?? 0}`);
135
+ }));
136
+ const errSpy = vi.spyOn(console, "error").mockImplementation(() => { });
137
+ const logSpy = vi.spyOn(console, "log").mockImplementation(() => { });
138
+ let exitCode = 0;
139
+ try {
140
+ main();
141
+ }
142
+ catch (e) {
143
+ const m = /__exit__:(\d+)/.exec(e.message);
144
+ exitCode = m ? Number(m[1]) : null;
145
+ }
146
+ const stderr = errSpy.mock.calls.map((c) => c.join(" ")).join("\n");
147
+ exitSpy.mockRestore();
148
+ errSpy.mockRestore();
149
+ logSpy.mockRestore();
150
+ return { exitCode, stderr };
151
+ };
152
+ // Positive: unscoped, no --dir
153
+ it("unscoped name: dir = name, pkg.name = name", () => {
154
+ const r = runMain(["my-auth"]);
155
+ expect(r.exitCode).toBe(0);
156
+ const pkg = JSON.parse(readFileSync(join(workdir, "my-auth", "package.json"), "utf-8"));
157
+ expect(pkg.name).toBe("my-auth");
158
+ });
159
+ // Positive: scoped, no --dir
160
+ it("scoped name: dir = pkg part, pkg.name = full scoped", () => {
161
+ const r = runMain(["@piratis-blossoms/auth.provider"]);
162
+ expect(r.exitCode).toBe(0);
163
+ const pkg = JSON.parse(readFileSync(join(workdir, "auth.provider", "package.json"), "utf-8"));
164
+ expect(pkg.name).toBe("@piratis-blossoms/auth.provider");
165
+ });
166
+ // Positive: scoped + --dir space
167
+ it("scoped name with --dir <val>: dir = val, pkg.name = full scoped", () => {
168
+ const r = runMain(["@piratis-blossoms/auth.provider", "--dir", "provider"]);
169
+ expect(r.exitCode).toBe(0);
170
+ const pkg = JSON.parse(readFileSync(join(workdir, "provider", "package.json"), "utf-8"));
171
+ expect(pkg.name).toBe("@piratis-blossoms/auth.provider");
172
+ });
173
+ // Positive: scoped + --dir= equals form
174
+ it("scoped name with --dir=<val>: dir = val, pkg.name = full scoped", () => {
175
+ const r = runMain(["@piratis-blossoms/auth.provider", "--dir=provider2"]);
176
+ expect(r.exitCode).toBe(0);
177
+ const pkg = JSON.parse(readFileSync(join(workdir, "provider2", "package.json"), "utf-8"));
178
+ expect(pkg.name).toBe("@piratis-blossoms/auth.provider");
179
+ });
180
+ // Positive: unscoped + --dir
181
+ it("unscoped name with --dir <val>: dir = val, pkg.name = unscoped", () => {
182
+ const r = runMain(["my-auth", "--dir", "custom"]);
183
+ expect(r.exitCode).toBe(0);
184
+ const pkg = JSON.parse(readFileSync(join(workdir, "custom", "package.json"), "utf-8"));
185
+ expect(pkg.name).toBe("my-auth");
186
+ });
187
+ // Positive: flags may come before the positional
188
+ it("flags before positional: --dir custom my-auth", () => {
189
+ const r = runMain(["--dir", "custom", "my-auth"]);
190
+ expect(r.exitCode).toBe(0);
191
+ const pkg = JSON.parse(readFileSync(join(workdir, "custom", "package.json"), "utf-8"));
192
+ expect(pkg.name).toBe("my-auth");
193
+ });
194
+ // Negative cases
195
+ it.each([
196
+ { case: "no args", args: [] },
197
+ { case: "two positionals", args: ["foo", "bar"] },
198
+ { case: "dot", args: ["."] },
199
+ { case: "dotdot", args: [".."] },
200
+ { case: "backslash in name", args: ["back\\slash"] },
201
+ { case: "empty scope", args: ["@/pkg"] },
202
+ { case: "empty pkg", args: ["@scope/"] },
203
+ { case: "double slash", args: ["@scope//pkg"] },
204
+ { case: "name too long", args: ["a".repeat(215)] },
205
+ { case: "--dir invalid (dot)", args: ["foo", "--dir", "."] },
206
+ { case: "--dir invalid (slash)", args: ["foo", "--dir", "a/b"] },
207
+ { case: "--dir invalid (at)", args: ["foo", "--dir", "@foo"] },
208
+ { case: "--dir invalid (back)", args: ["foo", "--dir", "a\\b"] },
209
+ { case: "--dir empty space form", args: ["foo", "--dir", ""] },
210
+ { case: "--dir empty equals form", args: ["foo", "--dir="] },
211
+ { case: "--dir missing value", args: ["foo", "--dir"] },
212
+ { case: "--dir duplicated", args: ["foo", "--dir", "a", "--dir", "b"] },
213
+ { case: "unknown flag", args: ["foo", "--unknown"] },
214
+ { case: "literal double-dash", args: ["foo", "--"] },
215
+ ])("rejects: $case", ({ args }) => {
216
+ const r = runMain(args);
217
+ expect(r.exitCode).toBe(1);
218
+ expect(r.stderr.length).toBeGreaterThan(0);
219
+ });
220
+ it("target directory already exists", () => {
221
+ mkdirSync(join(workdir, "foo"));
222
+ const r = runMain(["foo"]);
223
+ expect(r.exitCode).toBe(1);
224
+ expect(r.stderr.length).toBeGreaterThan(0);
225
+ });
226
+ });
package/dist/cli.d.mts ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=cli.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.mts","sourceRoot":"","sources":["../src/cli.mts"],"names":[],"mappings":""}
package/dist/cli.mjs ADDED
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env node
2
+ /*
3
+ * Copyright 2026 1o1 Co. Ltd.
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ import { main } from "./index.mjs";
18
+ main();
@@ -0,0 +1,5 @@
1
+ export declare const isValidProjectName: (name: string) => boolean;
2
+ export declare const isValidDirName: (name: string) => boolean;
3
+ export declare const scaffold: (targetDir: string, projectName: string) => void;
4
+ export declare const main: () => void;
5
+ //# sourceMappingURL=index.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.mts","sourceRoot":"","sources":["../src/index.mts"],"names":[],"mappings":"AAkCA,eAAO,MAAM,kBAAkB,GAAI,MAAM,MAAM,KAAG,OAIjD,CAAC;AAEF,eAAO,MAAM,cAAc,GAAI,MAAM,MAAM,KAAG,OAI7C,CAAC;AAEF,eAAO,MAAM,QAAQ,GAAI,WAAW,MAAM,EAAE,aAAa,MAAM,KAAG,IA0CjE,CAAC;AAsDF,eAAO,MAAM,IAAI,QAAO,IA+CvB,CAAC"}
package/dist/index.mjs ADDED
@@ -0,0 +1,166 @@
1
+ /*
2
+ * Copyright 2026 1o1 Co. Ltd.
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+ import { cpSync, existsSync, readFileSync, writeFileSync } from "node:fs";
17
+ import { dirname, resolve, sep } from "node:path";
18
+ import { fileURLToPath } from "node:url";
19
+ const __dirname = dirname(fileURLToPath(import.meta.url));
20
+ const TEMPLATES_DIR = resolve(__dirname, "../templates/standalone");
21
+ const getPackageVersions = () => {
22
+ const versionFile = resolve(__dirname, "../templates/versions.json");
23
+ if (existsSync(versionFile)) {
24
+ return JSON.parse(readFileSync(versionFile, "utf-8"));
25
+ }
26
+ return {};
27
+ };
28
+ const UNSCOPED_NAME_RE = /^[a-z0-9][a-z0-9-._~]*$/;
29
+ const SCOPED_NAME_RE = /^@[a-z0-9][a-z0-9-._~]*\/[a-z0-9][a-z0-9-._~]*$/;
30
+ const MAX_NAME_LEN = 214;
31
+ export const isValidProjectName = (name) => {
32
+ if (name.length === 0 || name.length > MAX_NAME_LEN)
33
+ return false;
34
+ if (name === "." || name === "..")
35
+ return false;
36
+ return UNSCOPED_NAME_RE.test(name) || SCOPED_NAME_RE.test(name);
37
+ };
38
+ export const isValidDirName = (name) => {
39
+ if (name.length === 0 || name.length > MAX_NAME_LEN)
40
+ return false;
41
+ if (name === "." || name === "..")
42
+ return false;
43
+ return UNSCOPED_NAME_RE.test(name);
44
+ };
45
+ export const scaffold = (targetDir, projectName) => {
46
+ if (!existsSync(TEMPLATES_DIR)) {
47
+ throw new Error(`Template directory not found at ${TEMPLATES_DIR}. If developing locally, run the prebuild script first.`);
48
+ }
49
+ // Copy template to target
50
+ const EXCLUDED_DIRS = new Set(["node_modules", "dist"]);
51
+ cpSync(TEMPLATES_DIR, targetDir, {
52
+ recursive: true,
53
+ filter: (source) => {
54
+ const segments = source.split(sep);
55
+ return !segments.some((s) => EXCLUDED_DIRS.has(s));
56
+ },
57
+ });
58
+ // Rewrite package.json
59
+ const pkgPath = resolve(targetDir, "package.json");
60
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
61
+ pkg.name = projectName;
62
+ delete pkg.private;
63
+ // Replace all workspace:* references with per-package published versions
64
+ const versions = getPackageVersions();
65
+ for (const section of ["dependencies", "devDependencies", "peerDependencies"]) {
66
+ const deps = pkg[section];
67
+ if (!deps)
68
+ continue;
69
+ for (const [name, version] of Object.entries(deps)) {
70
+ if (version === "workspace:*") {
71
+ const resolved = versions[name];
72
+ if (!resolved) {
73
+ throw new Error(`Cannot resolve version for workspace dependency "${name}". Ensure versions.json includes this package.`);
74
+ }
75
+ deps[name] = `^${resolved}`;
76
+ }
77
+ }
78
+ }
79
+ writeFileSync(pkgPath, `${JSON.stringify(pkg, null, 2)}\n`);
80
+ };
81
+ const parseArgs = (args) => {
82
+ const positionals = [];
83
+ let dir;
84
+ let dirSeen = false;
85
+ for (let i = 0; i < args.length; i++) {
86
+ const a = args[i];
87
+ if (a === "--dir") {
88
+ if (dirSeen)
89
+ throw new Error("--dir specified more than once");
90
+ if (i + 1 >= args.length)
91
+ throw new Error("--dir requires a value");
92
+ dir = args[i + 1];
93
+ dirSeen = true;
94
+ i++;
95
+ }
96
+ else if (a.startsWith("--dir=")) {
97
+ if (dirSeen)
98
+ throw new Error("--dir specified more than once");
99
+ dir = a.slice("--dir=".length);
100
+ dirSeen = true;
101
+ }
102
+ else if (a.startsWith("-")) {
103
+ // Treats `--` and any --unknown as an unknown flag.
104
+ throw new Error(`unknown flag: ${a}`);
105
+ }
106
+ else {
107
+ positionals.push(a);
108
+ }
109
+ }
110
+ if (positionals.length === 0)
111
+ throw new Error("missing <project-name>");
112
+ if (positionals.length > 1)
113
+ throw new Error("too many positional arguments");
114
+ return { projectName: positionals[0], dir };
115
+ };
116
+ const deriveDirName = (projectName, dir) => {
117
+ if (dir !== undefined)
118
+ return dir;
119
+ if (projectName.startsWith("@")) {
120
+ const pkgPart = projectName.split("/")[1];
121
+ if (!pkgPart) {
122
+ // Unreachable when projectName has passed isValidProjectName (SCOPED_NAME_RE
123
+ // guarantees a non-empty package segment after the single "/"). Guarded here
124
+ // so refactors that reorder validation cannot silently produce undefined.
125
+ throw new Error(`invariant: unvalidated scoped name ${projectName}`);
126
+ }
127
+ return pkgPart;
128
+ }
129
+ return projectName;
130
+ };
131
+ // CLI entry point
132
+ export const main = () => {
133
+ const args = process.argv.slice(2);
134
+ let parsed;
135
+ try {
136
+ parsed = parseArgs(args);
137
+ }
138
+ catch (e) {
139
+ console.error(`Error: ${e.message}`);
140
+ console.error("Usage: @o3co/create-auth-provider <project-name> [--dir <dir-name>]");
141
+ console.error("<project-name> must be a valid npm package name (scoped like @scope/pkg, or unscoped).");
142
+ process.exit(1);
143
+ }
144
+ const { projectName, dir } = parsed;
145
+ if (!isValidProjectName(projectName)) {
146
+ console.error("Error: <project-name> must be a valid npm package name (scoped like @scope/pkg, or unscoped; max 214 chars; no backslashes; no extra '/' beyond the single scope separator).");
147
+ process.exit(1);
148
+ }
149
+ if (dir !== undefined && !isValidDirName(dir)) {
150
+ console.error("Error: --dir must be a valid unscoped package name (no '/', '\\', '@'; not '.' or '..'; max 214 chars).");
151
+ process.exit(1);
152
+ }
153
+ const dirName = deriveDirName(projectName, dir);
154
+ const targetDir = resolve(process.cwd(), dirName);
155
+ if (existsSync(targetDir)) {
156
+ console.error(`Error: Directory '${dirName}' already exists.`);
157
+ process.exit(1);
158
+ }
159
+ console.log(`Creating ${projectName}...`);
160
+ scaffold(targetDir, projectName);
161
+ console.log(`\nDone! Created ${projectName} at ${targetDir}`);
162
+ console.log(`\nNext steps:`);
163
+ console.log(` cd ${dirName}`);
164
+ console.log(" npm install");
165
+ console.log(" npm run debug");
166
+ };
package/package.json ADDED
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "@o3co/create-auth-provider",
3
+ "version": "0.4.1",
4
+ "description": "Scaffold a new o3co/auth.provider project from the standalone template",
5
+ "license": "Apache-2.0",
6
+ "type": "module",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/o3co/auth.provider.git",
10
+ "directory": "create-app"
11
+ },
12
+ "bin": {
13
+ "create-auth-provider": "./dist/cli.mjs"
14
+ },
15
+ "files": [
16
+ "dist",
17
+ "templates"
18
+ ],
19
+ "scripts": {
20
+ "build": "tsc",
21
+ "prebuild": "node scripts/copy-templates.mjs",
22
+ "test": "vitest run"
23
+ },
24
+ "devDependencies": {
25
+ "@types/node": "^25.1.0",
26
+ "vitest": "^4.1.2"
27
+ }
28
+ }